2013-07-03 40 views
24

Tôi đang tìm thư viện Java để trích xuất từ ​​khóa từ một khối văn bản.Thư viện Java để trích xuất từ ​​khóa từ văn bản đầu vào

Quá trình này nên thực hiện như sau:

dừng từ làm sạch -> bắt nguồn -> tìm kiếm từ khóa dựa trên ngôn ngữ tiếng Anh thông tin thống kê - có nghĩa là nếu một từ xuất hiện nhiều lần trong văn bản hơn bằng tiếng Anh về xác suất hơn là một ứng cử viên từ khóa.

Có thư viện nào thực hiện tác vụ này không?

Trả lời

31

Đây là giải pháp có thể sử dụng Apache Lucene. Tôi không sử dụng phiên bản cuối cùng nhưng là 3.6.2 one, vì đây là phiên bản tôi biết rõ nhất. Bên cạnh /lucene-core-x.x.x.jar, đừng quên thêm /contrib/analyzers/common/lucene-analyzers-x.x.x.jar từ lưu trữ đã tải xuống vào dự án của bạn: nó chứa các trình phân tích ngôn ngữ cụ thể (đặc biệt là trình phân tích tiếng Anh trong trường hợp của bạn).

Lưu ý rằng điều này sẽ chỉ tìm tần suất của các từ văn bản nhập dựa trên gốc tương ứng. So sánh các tần số này với số liệu thống kê tiếng Anh sẽ được thực hiện sau đó (this answer có thể giúp bạn).


Các mô hình dữ liệu

Một từ khóa cho một gốc. Các từ khác nhau có thể có cùng một gốc, do đó tập hợp terms. Tần suất từ ​​khóa được tăng lên mỗi lần một thuật ngữ mới được tìm thấy (ngay cả khi nó đã được tìm thấy - một tập hợp tự động loại bỏ các bản sao).

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

} 

Utilities

Để ngăn chặn từ:

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

} 

Để tìm kiếm vào một bộ sưu tập (sẽ được sử dụng bởi danh sách các từ khóa tiềm năng):

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

Lõi

Dưới đây là phương pháp đầu vào chính:

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

} 

Ví dụ

Sử dụng phương pháp guessFromString trên Java wikipedia article introduction part, đây là danh sách 10 từ khóa thường gặp nhất đầu tiên (tức xuất phát) đã được tìm thấy:

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] 

lặp trên danh sách đầu ra để biết được là gốc tìm thấy từ cho mỗi gốc bằng cách nhận các terms bộ (hiển thị giữa dấu ngoặc [...] trong ví dụ trên).


gì tiếp theo

So sánh tần gốc/tần số tổng hợp tỷ lệ với số liệu thống kê ngôn ngữ người Anh, và giữ cho tôi trong vòng lặp nếu bạn quản lý nó: tôi có thể là khá quan tâm quá :)

+0

Vì vậy, như tôi hiểu, mã phải chạy trên một máy chủ apache. Điều gì xảy ra nếu phần mềm của tôi được cho là địa phương? – Shay

+0

@Shay Tại sao nó cần một máy chủ apache? Tôi chỉ cần đặt 'KeywordGuesser.guessFromString (" input ")' vào phương thức 'public static void (String [] args)' để tạo ra ví dụ. – sp00m

+0

Tôi không quen với Lucene và tôi thấy rằng mã này phụ thuộc rất nhiều vào nó nên tôi đã giả định rằng đây là trường hợp. Bất kỳ ý tưởng nào về từ điển tiếng Anh của thân cây có thể được tìm thấy? – Shay

1

Có thể bạn nên xem Lucene hoặc một số dự án/thư viện khác được xây dựng trên đầu trang.

+3

Đây là IMHO đường mòn tốt, nhưng nó sẽ là tuyệt vời nếu bạn có thể đưa ra một số ví dụ. Nó đúng hơn là một bình luận thực sự. – sp00m

+0

Trong khi liên kết này có thể trả lời câu hỏi, tốt hơn nên bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở thành không hợp lệ nếu trang được liên kết thay đổi. - [Từ đánh giá] (/ review/low-quality-posts/18072327) – midhunhk

3

Phiên bản mã được cập nhật và sẵn sàng sử dụng được đề xuất ở trên.
Mã này tương thích với Apache Lucene 5.x… 6.x.

CardKeyword lớp:

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 lớp:

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

Cuộc gọi của chức năng:

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

Hoạt động ... Cảm ơn. –

+0

Tôi đã thử mã này và không thể chạy đúng theo Lucene 6.x. Tôi đã phải thêm một số cuộc gọi đặt lại() trên luồng mã thông báo. Ngoài ra, nó không có vẻ để xử lý các từ dashed đúng ... Tôi nhận thấy rằng một thuật ngữ như "công nghiệp được công nhận" đã được thay thế bằng "công nghiệp-0recognized" trong một nỗ lực để ngăn chặn các tokenizer từ phá vỡ từ, nhưng tôi vẫn có một mã thông báo "0recognized" để hack dường như không hoạt động. –

+0

Tôi đã có thể vượt qua vấn đề với StandardTokenizer bằng cách sử dụng Trình kiểm tra khoảng trắng. Dường như nó có thể xử lý các từ bị gạch ngang mà không cần đến hack. –

Các vấn đề liên quan