2013-07-03 9 views
24

Sto cercando una libreria Java per estrarre le parole chiave da un blocco di testo.Libreria Java per l'estrazione di parole chiave dal testo di input

Il processo dovrebbe essere la seguente: la pulizia parola

stop -> derivante -> ricerca per le parole chiave sulla base di linguistica inglese delle informazioni statistiche - che significa se una parola compare più volte nel testo che nella lingua inglese in termini di probabilità rispetto a una parola chiave candidata.

Esiste una libreria che esegue questa attività?

risposta

31

Ecco una possibile soluzione utilizzando Apache Lucene. Non ho usato l'ultima versione ma lo 3.6.2 one, poiché questo è quello che conosco meglio. Oltre allo /lucene-core-x.x.x.jar, non dimenticare di aggiungere lo /contrib/analyzers/common/lucene-analyzers-x.x.x.jar dall'archivio scaricato al progetto: contiene gli analizzatori specifici della lingua (in particolare quello inglese nel tuo caso).

Si noti che questo sarà solo trovare le frequenze delle parole di testo di input in base alla rispettiva radice. Confrontando queste frequenze con le statistiche della lingua inglese deve essere fatto in seguito (this answer può aiutare a proposito).


Il modello di dati

Una parola chiave per uno stelo. Parole diverse possono avere la stessa radice, quindi il set terms. La frequenza della parola chiave viene incrementata ogni volta che viene trovato un nuovo termine (anche se è già stato trovato - un set rimuove automaticamente i duplicati).

public class Keyword implements Comparable<Keyword> { 

    private final String stem; 
    private final Set<String> terms = new HashSet<String>(); 
    private int frequency = 0; 

    public Keyword(String stem) { 
    this.stem = stem; 
    } 

    public void add(String term) { 
    terms.add(term); 
    frequency++; 
    } 

    @Override 
    public int compareTo(Keyword o) { 
    // descending order 
    return Integer.valueOf(o.frequency).compareTo(frequency); 
    } 

    @Override 
    public boolean equals(Object obj) { 
    if (this == obj) { 
     return true; 
    } else if (!(obj instanceof Keyword)) { 
     return false; 
    } else { 
     return stem.equals(((Keyword) obj).stem); 
    } 
    } 

    @Override 
    public int hashCode() { 
    return Arrays.hashCode(new Object[] { stem }); 
    } 

    public String getStem() { 
    return stem; 
    } 

    public Set<String> getTerms() { 
    return terms; 
    } 

    public int getFrequency() { 
    return frequency; 
    } 

} 

Utilità

Per arginare una parola:

public static String stem(String term) throws IOException { 

    TokenStream tokenStream = null; 
    try { 

    // tokenize 
    tokenStream = new ClassicTokenizer(Version.LUCENE_36, new StringReader(term)); 
    // stem 
    tokenStream = new PorterStemFilter(tokenStream); 

    // add each token in a set, so that duplicates are removed 
    Set<String> stems = new HashSet<String>(); 
    CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class); 
    tokenStream.reset(); 
    while (tokenStream.incrementToken()) { 
     stems.add(token.toString()); 
    } 

    // if no stem or 2+ stems have been found, return null 
    if (stems.size() != 1) { 
     return null; 
    } 
    String stem = stems.iterator().next(); 
    // if the stem has non-alphanumerical chars, return null 
    if (!stem.matches("[a-zA-Z0-9-]+")) { 
     return null; 
    } 

    return stem; 

    } finally { 
    if (tokenStream != null) { 
     tokenStream.close(); 
    } 
    } 

} 

Per cercare in una collezione (saranno utilizzati da parte l'elenco dei potenziali parole chiave):

public static <T> T find(Collection<T> collection, T example) { 
    for (T element : collection) { 
    if (element.equals(example)) { 
     return element; 
    } 
    } 
    collection.add(example); 
    return example; 
} 

Nucleo

Ecco il metodo di alimentazione principale:

public static List<Keyword> guessFromString(String input) throws IOException { 

    TokenStream tokenStream = null; 
    try { 

    // hack to keep dashed words (e.g. "non-specific" rather than "non" and "specific") 
    input = input.replaceAll("-+", "-0"); 
    // replace any punctuation char but apostrophes and dashes by a space 
    input = input.replaceAll("[\\p{Punct}&&[^'-]]+", " "); 
    // replace most common english contractions 
    input = input.replaceAll("(?:'(?:[tdsm]|[vr]e|ll))+\\b", ""); 

    // tokenize input 
    tokenStream = new ClassicTokenizer(Version.LUCENE_36, new StringReader(input)); 
    // to lowercase 
    tokenStream = new LowerCaseFilter(Version.LUCENE_36, tokenStream); 
    // remove dots from acronyms (and "'s" but already done manually above) 
    tokenStream = new ClassicFilter(tokenStream); 
    // convert any char to ASCII 
    tokenStream = new ASCIIFoldingFilter(tokenStream); 
    // remove english stop words 
    tokenStream = new StopFilter(Version.LUCENE_36, tokenStream, EnglishAnalyzer.getDefaultStopSet()); 

    List<Keyword> keywords = new LinkedList<Keyword>(); 
    CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class); 
    tokenStream.reset(); 
    while (tokenStream.incrementToken()) { 
     String term = token.toString(); 
     // stem each term 
     String stem = stem(term); 
     if (stem != null) { 
     // create the keyword or get the existing one if any 
     Keyword keyword = find(keywords, new Keyword(stem.replaceAll("-0", "-"))); 
     // add its corresponding initial token 
     keyword.add(term.replaceAll("-0", "-")); 
     } 
    } 

    // reverse sort by frequency 
    Collections.sort(keywords); 

    return keywords; 

    } finally { 
    if (tokenStream != null) { 
     tokenStream.close(); 
    } 
    } 

} 

Esempio

Utilizzando il metodo guessFromString sulla Java wikipedia article introduction part, ecco le prime 10 parole chiave più frequenti (cioè steli) che sono stati trovati:

java   x12 [java] 
compil  x5  [compiled, compiler, compilers] 
sun   x5  [sun] 
develop  x4  [developed, developers] 
languag  x3  [languages, language] 
implement x3  [implementation, implementations] 
applic  x3  [application, applications] 
run   x3  [run] 
origin  x3  [originally, original] 
gnu   x3  [gnu] 

iterare la lista di output per sapere che erano l'originale trovato parole per ogni stelo da ottenere le terms set (visualizzati tra parentesi [...] nell'esempio di cui sopra).


Quali sono le prospettive

Confronta i frequenza staminali/frequenze riassumono rapporti con le statistiche di lingua inglese quelli, e mi tengono in loop se il vostro riusciti: ho potuto essere molto interessato troppo :)

+0

Quindi, come ho capito, il codice deve essere eseguito su un server Apache. Cosa succede se il mio software dovrebbe essere locale? – Shay

+0

@Shay Perché sarebbe necessario un server Apache? Ho appena messo 'KeywordsGuesser.guessFromString (" input ")' in un metodo 'public static void main (String [] args)' per fare l'esempio. – sp00m

+0

Non conosco Lucene e vedo che il codice è fortemente basato su di esso, quindi ho pensato che fosse così. Qualche idea su dove trovare questo dizionario inglese di stems? – Shay

1

Forse dovresti guardare su Lucene o su altri progetti/librerie su di esso.

+3

Questa è la buona pista IMHO, ma sarebbe bello se potessi dare qualche esempio. Dovrebbe piuttosto essere un commento come è in realtà. – sp00m

+0

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il link per riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. - [Dalla recensione] (/ recensione/post di bassa qualità/18072327) – midhunhk

3

Una versione aggiornata e pronta all'uso del codice proposto sopra.
Questo codice è compatibile con Apache Lucene 5.x ... 6.x.

CardKeyword classe:

import java.util.HashSet; 
import java.util.Set; 

/** 
* Keyword card with stem form, terms dictionary and frequency rank 
*/ 
class CardKeyword implements Comparable<CardKeyword> { 

    /** 
    * Stem form of the keyword 
    */ 
    private final String stem; 

    /** 
    * Terms dictionary 
    */ 
    private final Set<String> terms = new HashSet<>(); 

    /** 
    * Frequency rank 
    */ 
    private int frequency; 

    /** 
    * Build keyword card with stem form 
    * 
    * @param stem 
    */ 
    public CardKeyword(String stem) { 
     this.stem = stem; 
    } 

    /** 
    * Add term to the dictionary and update its frequency rank 
    * 
    * @param term 
    */ 
    public void add(String term) { 
     this.terms.add(term); 
     this.frequency++; 
    } 

    /** 
    * Compare two keywords by frequency rank 
    * 
    * @param keyword 
    * @return int, which contains comparison results 
    */ 
    @Override 
    public int compareTo(CardKeyword keyword) { 
     return Integer.valueOf(keyword.frequency).compareTo(this.frequency); 
    } 

    /** 
    * Get stem's hashcode 
    * 
    * @return int, which contains stem's hashcode 
    */ 
    @Override 
    public int hashCode() { 
     return this.getStem().hashCode(); 
    } 

    /** 
    * Check if two stems are equal 
    * 
    * @param o 
    * @return boolean, true if two stems are equal 
    */ 
    @Override 
    public boolean equals(Object o) { 

     if (this == o) return true; 

     if (!(o instanceof CardKeyword)) return false; 

     CardKeyword that = (CardKeyword) o; 

     return this.getStem().equals(that.getStem()); 
    } 

    /** 
    * Get stem form of keyword 
    * 
    * @return String, which contains getStemForm form 
    */ 
    public String getStem() { 
     return this.stem; 
    } 

    /** 
    * Get terms dictionary of the stem 
    * 
    * @return Set<String>, which contains set of terms of the getStemForm 
    */ 
    public Set<String> getTerms() { 
     return this.terms; 
    } 

    /** 
    * Get stem frequency rank 
    * 
    * @return int, which contains getStemForm frequency 
    */ 
    public int getFrequency() { 
     return this.frequency; 
    } 
} 

KeywordsExtractor classe:

import org.apache.lucene.analysis.TokenStream; 
import org.apache.lucene.analysis.core.LowerCaseFilter; 
import org.apache.lucene.analysis.core.StopFilter; 
import org.apache.lucene.analysis.en.EnglishAnalyzer; 
import org.apache.lucene.analysis.en.PorterStemFilter; 
import org.apache.lucene.analysis.miscellaneous.ASCIIFoldingFilter; 
import org.apache.lucene.analysis.standard.ClassicFilter; 
import org.apache.lucene.analysis.standard.StandardTokenizer; 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; 

import java.io.IOException; 
import java.io.StringReader; 
import java.util.*; 

/** 
* Keywords extractor functionality handler 
*/ 
class KeywordsExtractor { 

    /** 
    * Get list of keywords with stem form, frequency rank, and terms dictionary 
    * 
    * @param fullText 
    * @return List<CardKeyword>, which contains keywords cards 
    * @throws IOException 
    */ 
    static List<CardKeyword> getKeywordsList(String fullText) throws IOException { 

     TokenStream tokenStream = null; 

     try { 
      // treat the dashed words, don't let separate them during the processing 
      fullText = fullText.replaceAll("-+", "-0"); 

      // replace any punctuation char but apostrophes and dashes with a space 
      fullText = fullText.replaceAll("[\\p{Punct}&&[^'-]]+", " "); 

      // replace most common English contractions 
      fullText = fullText.replaceAll("(?:'(?:[tdsm]|[vr]e|ll))+\\b", ""); 

      StandardTokenizer stdToken = new StandardTokenizer(); 
      stdToken.setReader(new StringReader(fullText)); 

      tokenStream = new StopFilter(new ASCIIFoldingFilter(new ClassicFilter(new LowerCaseFilter(stdToken))), EnglishAnalyzer.getDefaultStopSet()); 
      tokenStream.reset(); 

      List<CardKeyword> cardKeywords = new LinkedList<>(); 

      CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class); 

      while (tokenStream.incrementToken()) { 

       String term = token.toString(); 
       String stem = getStemForm(term); 

       if (stem != null) { 
        CardKeyword cardKeyword = find(cardKeywords, new CardKeyword(stem.replaceAll("-0", "-"))); 
        // treat the dashed words back, let look them pretty 
        cardKeyword.add(term.replaceAll("-0", "-")); 
       } 
      } 

      // reverse sort by frequency 
      Collections.sort(cardKeywords); 

      return cardKeywords; 
     } finally { 
      if (tokenStream != null) { 
       try { 
        tokenStream.close(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 

    /** 
    * Get stem form of the term 
    * 
    * @param term 
    * @return String, which contains the stemmed form of the term 
    * @throws IOException 
    */ 
    private static String getStemForm(String term) throws IOException { 

     TokenStream tokenStream = null; 

     try { 
      StandardTokenizer stdToken = new StandardTokenizer(); 
      stdToken.setReader(new StringReader(term)); 

      tokenStream = new PorterStemFilter(stdToken); 
      tokenStream.reset(); 

      // eliminate duplicate tokens by adding them to a set 
      Set<String> stems = new HashSet<>(); 

      CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class); 

      while (tokenStream.incrementToken()) { 
       stems.add(token.toString()); 
      } 

      // if stem form was not found or more than 2 stems have been found, return null 
      if (stems.size() != 1) { 
       return null; 
      } 

      String stem = stems.iterator().next(); 

      // if the stem form has non-alphanumerical chars, return null 
      if (!stem.matches("[a-zA-Z0-9-]+")) { 
       return null; 
      } 

      return stem; 
     } finally { 
      if (tokenStream != null) { 
       try { 
        tokenStream.close(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 

    /** 
    * Find sample in collection 
    * 
    * @param collection 
    * @param sample 
    * @param <T> 
    * @return <T> T, which contains the found object within collection if exists, otherwise the initially searched object 
    */ 
    private static <T> T find(Collection<T> collection, T sample) { 

     for (T element : collection) { 
      if (element.equals(sample)) { 
       return element; 
      } 
     } 

     collection.add(sample); 

     return sample; 
    } 
} 

La chiamata della funzione:

String text = "…"; 
List<CardKeyword> keywordsList = KeywordsExtractor.getKeywordsList(text); 
+0

Works ... Grazie. –

+0

Ho provato questo codice e non è riuscito a funzionare correttamente con Lucene 6.x. Ho dovuto aggiungere alcune chiamate reset() sul flusso di token. Inoltre, non sembra gestire correttamente le parole tratteggiate ... Ho notato che un termine come "riconosciuto dal settore" è stato sostituito con "riconosciuto dall'industria" nel tentativo di impedire al tokenizzatore di suddividere la parola, ma io ancora ha ottenuto un token "0 riconosciuto" in modo che l'hack non sembrasse funzionare. –

+0

Sono stato in grado di superare il problema con StandardTokenizer utilizzando WhitespaceTokenizer. Sembrava gestire le parole tratteggiate senza problemi. –

Problemi correlati