20191026のJavaに関する記事は10件です。

[004] Javaで自然言語処理と構文解析の統計処理を使ってテキスト分析をしてみる

Indexに戻る
[003]品詞の統計処理 > [004]構文解析の統計処理 > 次のページ

NLP4Jを使って形態素解析の結果と簡単な統計処理を利用してテキスト分析をしてみます。

繰り返しになりますが、
「形態素解析」や「構文解析」は料理で言うと「包丁の使い方」に近い感じです。
「形態素解析」や「構文解析」に加えて「統計処理」を入れるとテキスト分析=料理に近くなるのではないかなと思います。
ここでの統計処理は簡単なものを利用しますが、機械学習や複雑な統計処理を入れてみるのもよいと思います。

さて、ここで以下のような文書があったとします。1行が1レコードです。

"Toyota", "ハイブリッドカーを作っています。"
"Toyota", "ハイブリッドカーを売っています。"
"Toyota", "自動車を作っています。"
"Toyota", "自動車を売っています。"
"Nissan", "EVを作っています。"
"Nissan", "EVを売っています。"
"Nissan", "自動車を売っています。"
"Nissan", "ルノーと提携しています。"
"Nissan", "軽自動車を売っています。"
"Honda", "自動車を作っています。"
"Honda", "自動車を売っています。"
"Honda", "バイクを作っています。"
"Honda", "バイクを売っています。"
"Honda", "軽自動車を売っています。"
"Honda", "軽自動車を作っています。"

文書を「Toyota」「Nissan」「Honda」に分けて考えたとき、「特徴的な”係り受け”キーワード」は何でしょうか?
特徴的なキーワードをNLP4Jを使って出してみます。(難しい処理はしていません)
構文解析を使い、統計処理を「SimpleDocumentIndex」クラスを使って行っているところがポイントです。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

public class HelloTextMiningMain2B {
    public static void main(String[] args) throws Exception {
        // ドキュメントの用意(CSVを読み込むなどでも可)
        List<Document> docs = new ArrayList<Document>();
        {
            docs.add(createDocument("Toyota", "ハイブリッドカーを作っています。"));
            docs.add(createDocument("Toyota", "ハイブリッドカーを売っています。"));
            docs.add(createDocument("Toyota", "自動車を作っています。"));
            docs.add(createDocument("Toyota", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "EVを作っています。"));
            docs.add(createDocument("Nissan", "EVを売っています。"));
            docs.add(createDocument("Nissan", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "ルノーと提携しています。"));
            docs.add(createDocument("Nissan", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "自動車を作っています。"));
            docs.add(createDocument("Honda", "自動車を売っています。"));
            docs.add(createDocument("Honda", "バイクを作っています。"));
            docs.add(createDocument("Honda", "バイクを売っています。"));
            docs.add(createDocument("Honda", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "軽自動車を作っています。"));
        }
        // 形態素解析アノテーター+構文解析アノテーター
        DocumentAnnotator annotator = new YjpAllAnnotator(); // 形態素解析+構文解析
        {
            System.err.println("形態素解析+構文解析");
            long time1 = System.currentTimeMillis();
            // 形態素解析+構文解析
            annotator.annotate(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        // キーワードインデックス(統計処理)の用意
        Index index = new SimpleDocumentIndex();
        {
            System.err.println("インデックス作成");
            long time1 = System.currentTimeMillis();
            // キーワードインデックス作成処理
            index.addDocuments(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        {
            // 「名詞...動詞」でNissanに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Nissan");
            System.out.println("名詞...動詞 for Nissan");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でToyotaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Toyota");
            System.out.println("名詞...動詞 for Toyota");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でHondaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Honda");
            System.out.println("名詞...動詞 for Honda");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
    }
    static Document createDocument(String item, String text) {
        Document doc = new DefaultDocument();
        doc.putAttribute("item", item);
        doc.setText(text);
        return doc;
    }
}

Output

形態素解析+構文解析
処理時間[ms]:9618
インデックス作成
処理時間[ms]:3
名詞...動詞 for Nissan
count=1,correlation=3.0,lex=EV...売る
count=1,correlation=3.0,lex=EV...作る
count=1,correlation=1.5,lex=軽自動車...売る
count=1,correlation=1.0,lex=自動車...売る
名詞...動詞 for Toyota
count=1,correlation=3.8,lex=カー...売る
count=1,correlation=3.8,lex=カー...作る
count=1,correlation=3.8,lex=ハイブリッド...売る
count=1,correlation=3.8,lex=ハイブリッド...作る
count=1,correlation=1.9,lex=自動車...作る
count=1,correlation=1.3,lex=自動車...売る
名詞...動詞 for Honda
count=1,correlation=2.5,lex=バイク...売る
count=1,correlation=2.5,lex=軽自動車...作る
count=1,correlation=2.5,lex=バイク...作る
count=1,correlation=1.3,lex=自動車...作る
count=1,correlation=1.3,lex=軽自動車...売る
count=1,correlation=0.8,lex=自動車...売る

簡単ですね!
このような結果になりました!
人間の感覚と合っているでしょうか?

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4Jのご紹介 - [004] Javaで自然言語処理と構文解析の統計処理を使ってテキスト分析をしてみる

Indexに戻る
[003]品詞の統計処理 > [004]構文解析の統計処理 > 次のページ

NLP4Jを使って形態素解析の結果と簡単な統計処理を利用してテキスト分析をしてみます。

繰り返しになりますが、
「形態素解析」や「構文解析」は料理で言うと「包丁の使い方」に近い感じです。
「形態素解析」や「構文解析」に加えて「統計処理」を入れるとテキスト分析=料理に近くなるのではないかなと思います。
ここでの統計処理は簡単なものを利用しますが、機械学習や複雑な統計処理を入れてみるのもよいと思います。

さて、ここで以下のような文書があったとします。1行が1レコードです。

"Toyota", "ハイブリッドカーを作っています。"
"Toyota", "ハイブリッドカーを売っています。"
"Toyota", "自動車を作っています。"
"Toyota", "自動車を売っています。"
"Nissan", "EVを作っています。"
"Nissan", "EVを売っています。"
"Nissan", "自動車を売っています。"
"Nissan", "ルノーと提携しています。"
"Nissan", "軽自動車を売っています。"
"Honda", "自動車を作っています。"
"Honda", "自動車を売っています。"
"Honda", "バイクを作っています。"
"Honda", "バイクを売っています。"
"Honda", "軽自動車を売っています。"
"Honda", "軽自動車を作っています。"

文書を「Toyota」「Nissan」「Honda」に分けて考えたとき、「特徴的な”係り受け”キーワード」は何でしょうか?
特徴的なキーワードをNLP4Jを使って出してみます。(難しい処理はしていません)
構文解析を使い、統計処理を「SimpleDocumentIndex」クラスを使って行っているところがポイントです。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

public class HelloTextMiningMain2B {
    public static void main(String[] args) throws Exception {
        // ドキュメントの用意(CSVを読み込むなどでも可)
        List<Document> docs = new ArrayList<Document>();
        {
            docs.add(createDocument("Toyota", "ハイブリッドカーを作っています。"));
            docs.add(createDocument("Toyota", "ハイブリッドカーを売っています。"));
            docs.add(createDocument("Toyota", "自動車を作っています。"));
            docs.add(createDocument("Toyota", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "EVを作っています。"));
            docs.add(createDocument("Nissan", "EVを売っています。"));
            docs.add(createDocument("Nissan", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "ルノーと提携しています。"));
            docs.add(createDocument("Nissan", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "自動車を作っています。"));
            docs.add(createDocument("Honda", "自動車を売っています。"));
            docs.add(createDocument("Honda", "バイクを作っています。"));
            docs.add(createDocument("Honda", "バイクを売っています。"));
            docs.add(createDocument("Honda", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "軽自動車を作っています。"));
        }
        // 形態素解析アノテーター+構文解析アノテーター
        DocumentAnnotator annotator = new YjpAllAnnotator(); // 形態素解析+構文解析
        {
            System.err.println("形態素解析+構文解析");
            long time1 = System.currentTimeMillis();
            // 形態素解析+構文解析
            annotator.annotate(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        // キーワードインデックス(統計処理)の用意
        Index index = new SimpleDocumentIndex();
        {
            System.err.println("インデックス作成");
            long time1 = System.currentTimeMillis();
            // キーワードインデックス作成処理
            index.addDocuments(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        {
            // 「名詞...動詞」でNissanに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Nissan");
            System.out.println("名詞...動詞 for Nissan");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でToyotaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Toyota");
            System.out.println("名詞...動詞 for Toyota");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でHondaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Honda");
            System.out.println("名詞...動詞 for Honda");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
    }
    static Document createDocument(String item, String text) {
        Document doc = new DefaultDocument();
        doc.putAttribute("item", item);
        doc.setText(text);
        return doc;
    }
}

Output

形態素解析+構文解析
処理時間[ms]:9618
インデックス作成
処理時間[ms]:3
名詞...動詞 for Nissan
count=1,correlation=3.0,lex=EV...売る
count=1,correlation=3.0,lex=EV...作る
count=1,correlation=1.5,lex=軽自動車...売る
count=1,correlation=1.0,lex=自動車...売る
名詞...動詞 for Toyota
count=1,correlation=3.8,lex=カー...売る
count=1,correlation=3.8,lex=カー...作る
count=1,correlation=3.8,lex=ハイブリッド...売る
count=1,correlation=3.8,lex=ハイブリッド...作る
count=1,correlation=1.9,lex=自動車...作る
count=1,correlation=1.3,lex=自動車...売る
名詞...動詞 for Honda
count=1,correlation=2.5,lex=バイク...売る
count=1,correlation=2.5,lex=軽自動車...作る
count=1,correlation=2.5,lex=バイク...作る
count=1,correlation=1.3,lex=自動車...作る
count=1,correlation=1.3,lex=軽自動車...売る
count=1,correlation=0.8,lex=自動車...売る

簡単ですね!
このような結果になりました!
人間の感覚と合っているでしょうか?

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J [004] Javaで自然言語処理と構文解析の統計処理を使ってテキスト分析をしてみる

Indexに戻る : [003]品詞の統計処理 > [004]構文解析の統計処理 > [005-1] NLP4J+Twitter4J(データ収集)

NLP4Jを使って形態素解析の結果と簡単な統計処理を利用してテキスト分析をしてみます。

繰り返しになりますが、
「形態素解析」や「構文解析」は料理で言うと「包丁の使い方」に近い感じです。
「形態素解析」や「構文解析」に加えて「統計処理」を入れるとテキスト分析=料理に近くなるのではないかなと思います。
ここでの統計処理は簡単なものを利用しますが、機械学習や複雑な統計処理を入れてみるのもよいと思います。

さて、ここで以下のような文書があったとします。1行が1レコードです。

"Toyota", "ハイブリッドカーを作っています。"
"Toyota", "ハイブリッドカーを売っています。"
"Toyota", "自動車を作っています。"
"Toyota", "自動車を売っています。"
"Nissan", "EVを作っています。"
"Nissan", "EVを売っています。"
"Nissan", "自動車を売っています。"
"Nissan", "ルノーと提携しています。"
"Nissan", "軽自動車を売っています。"
"Honda", "自動車を作っています。"
"Honda", "自動車を売っています。"
"Honda", "バイクを作っています。"
"Honda", "バイクを売っています。"
"Honda", "軽自動車を売っています。"
"Honda", "軽自動車を作っています。"

文書を「Toyota」「Nissan」「Honda」に分けて考えたとき、「特徴的な”係り受け”キーワード」は何でしょうか?
特徴的なキーワードをNLP4Jを使って出してみます。(難しい処理はしていません)
構文解析を使い、統計処理を「SimpleDocumentIndex」クラスを使って行っているところがポイントです。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

public class HelloTextMiningMain2B {
    public static void main(String[] args) throws Exception {
        // ドキュメントの用意(CSVを読み込むなどでも可)
        List<Document> docs = new ArrayList<Document>();
        {
            docs.add(createDocument("Toyota", "ハイブリッドカーを作っています。"));
            docs.add(createDocument("Toyota", "ハイブリッドカーを売っています。"));
            docs.add(createDocument("Toyota", "自動車を作っています。"));
            docs.add(createDocument("Toyota", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "EVを作っています。"));
            docs.add(createDocument("Nissan", "EVを売っています。"));
            docs.add(createDocument("Nissan", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "ルノーと提携しています。"));
            docs.add(createDocument("Nissan", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "自動車を作っています。"));
            docs.add(createDocument("Honda", "自動車を売っています。"));
            docs.add(createDocument("Honda", "バイクを作っています。"));
            docs.add(createDocument("Honda", "バイクを売っています。"));
            docs.add(createDocument("Honda", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "軽自動車を作っています。"));
        }
        // 形態素解析アノテーター+構文解析アノテーター
        DocumentAnnotator annotator = new YjpAllAnnotator(); // 形態素解析+構文解析
        {
            System.err.println("形態素解析+構文解析");
            long time1 = System.currentTimeMillis();
            // 形態素解析+構文解析
            annotator.annotate(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        // キーワードインデックス(統計処理)の用意
        Index index = new SimpleDocumentIndex();
        {
            System.err.println("インデックス作成");
            long time1 = System.currentTimeMillis();
            // キーワードインデックス作成処理
            index.addDocuments(docs);
            long time2 = System.currentTimeMillis();
            System.err.println("処理時間[ms]:" + (time2 - time1));
        }
        {
            // 「名詞...動詞」でNissanに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Nissan");
            System.out.println("名詞...動詞 for Nissan");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でToyotaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Toyota");
            System.out.println("名詞...動詞 for Toyota");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 「名詞...動詞」でHondaに共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞...動詞", "item=Honda");
            System.out.println("名詞...動詞 for Honda");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("count=%d,correlation=%.1f,lex=%s", kwd.getCount(),
                        kwd.getCorrelation(), kwd.getLex()));
            }
        }
    }
    static Document createDocument(String item, String text) {
        Document doc = new DefaultDocument();
        doc.putAttribute("item", item);
        doc.setText(text);
        return doc;
    }
}

Output

形態素解析+構文解析
処理時間[ms]:9618
インデックス作成
処理時間[ms]:3
名詞...動詞 for Nissan
count=1,correlation=3.0,lex=EV...売る
count=1,correlation=3.0,lex=EV...作る
count=1,correlation=1.5,lex=軽自動車...売る
count=1,correlation=1.0,lex=自動車...売る
名詞...動詞 for Toyota
count=1,correlation=3.8,lex=カー...売る
count=1,correlation=3.8,lex=カー...作る
count=1,correlation=3.8,lex=ハイブリッド...売る
count=1,correlation=3.8,lex=ハイブリッド...作る
count=1,correlation=1.9,lex=自動車...作る
count=1,correlation=1.3,lex=自動車...売る
名詞...動詞 for Honda
count=1,correlation=2.5,lex=バイク...売る
count=1,correlation=2.5,lex=軽自動車...作る
count=1,correlation=2.5,lex=バイク...作る
count=1,correlation=1.3,lex=自動車...作る
count=1,correlation=1.3,lex=軽自動車...売る
count=1,correlation=0.8,lex=自動車...売る

簡単ですね!
このような結果になりました!
人間の感覚と合っているでしょうか?


Indexに戻る : [003]品詞の統計処理 > [004]構文解析の統計処理 > [005-1] NLP4J+Twitter4J(データ収集)

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J のご紹介 - [003] Javaで自然言語処理と品詞の統計処理を使ってテキスト分析をしてみる

Indexに戻る
[002]構文解析 > [003]品詞の統計処理 > [004]構文解析の統計処理

NLP4Jを使って形態素解析の結果と簡単な統計処理を利用してテキスト分析をしてみます。

「形態素解析」や「構文解析」は料理で言うと「包丁の使い方」に近い感じです。
「形態素解析」や「構文解析」に加えて「統計処理」を入れるとテキスト分析=料理に近くなるのではないかなと思います。
ここでの統計処理は簡単なものを利用しますが、機械学習や複雑な統計処理を入れてみるのもよいと思います。

さて、ここで以下のような文書があったとします。1行が1レコードです。

"Toyota", "ハイブリッドカーを作っています。"
"Toyota", "ハイブリッドカーを売っています。"
"Toyota", "自動車を作っています。"
"Toyota", "自動車を売っています。"
"Nissan", "EVを作っています。"
"Nissan", "EVを売っています。"
"Nissan", "自動車を売っています。"
"Nissan", "ルノーと提携しています。"
"Nissan", "軽自動車を売っています。"
"Honda", "自動車を作っています。"
"Honda", "自動車を売っています。"
"Honda", "バイクを作っています。"
"Honda", "バイクを売っています。"
"Honda", "軽自動車を売っています。"
"Honda", "軽自動車を作っています。"

文書を「Toyota」「Nissan」「Honda」に分けて考えたとき、「特徴的なキーワード」は何でしょうか?
特徴的なキーワードをNLP4Jを使って出してみます。(難しい処理はしていません)
統計処理を「SimpleDocumentIndex」クラスを使って行っているところがポイントです。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

public class HelloTextMiningMain1 {
    public static void main(String[] args) throws Exception {
// ドキュメントの用意(CSVを読み込むなどでも可)
        List<Document> docs = new ArrayList<Document>();
        {
            docs.add(createDocument("Toyota", "ハイブリッドカーを作っています。"));
            docs.add(createDocument("Toyota", "ハイブリッドカーを売っています。"));
            docs.add(createDocument("Toyota", "自動車を作っています。"));
            docs.add(createDocument("Toyota", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "EVを作っています。"));
            docs.add(createDocument("Nissan", "EVを売っています。"));
            docs.add(createDocument("Nissan", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "ルノーと提携しています。"));
            docs.add(createDocument("Nissan", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "自動車を作っています。"));
            docs.add(createDocument("Honda", "自動車を売っています。"));
            docs.add(createDocument("Honda", "バイクを作っています。"));
            docs.add(createDocument("Honda", "バイクを売っています。"));
            docs.add(createDocument("Honda", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "軽自動車を作っています。"));
        }

// 形態素解析アノテーター
        DocumentAnnotator annotator = new YJpMaAnnotator();
// 形態素解析処理
        annotator.annotate(docs);

// キーワードインデックス(統計処理)の用意
        Index index = new SimpleDocumentIndex();
// キーワードインデックス作成処理
        index.addDocuments(docs);
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Nissan");
            System.out.println("Keywords(名詞) for Nissan");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Toyota");
            System.out.println("Keywords(名詞) for Toyota");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Honda");
            System.out.println("Keywords(名詞) for Honda");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
    }

    static Document createDocument(String item, String text) {
        Document doc = new DefaultDocument();
        doc.putAttribute("item", item);
        doc.setText(text);
        return doc;
    }

}

Output

Keywords(名詞) for Nissan
3.0,EV
3.0,ルノー
3.0,提携
1.0,軽自動車
0.6,自動車
Keywords(名詞) for Toyota
3.8,ハイブリッド
3.8,カー
1.5,自動車
Keywords(名詞) for Honda
2.5,バイク
1.7,軽自動車
1.0,自動車

簡単ですね!
このような結果になりました!
人間の感覚と合っているでしょうか?

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J [003] Javaで自然言語処理と品詞の統計処理を使ってテキスト分析をしてみる

Indexに戻る : [002]構文解析 > [003]品詞の統計処理 > [004]構文解析の統計処理

NLP4Jを使って形態素解析の結果と簡単な統計処理を利用してテキスト分析をしてみます。

「形態素解析」や「構文解析」は料理で言うと「包丁の使い方」に近い感じです。
「形態素解析」や「構文解析」に加えて「統計処理」を入れるとテキスト分析=料理に近くなるのではないかなと思います。
ここでの統計処理は簡単なものを利用しますが、機械学習や複雑な統計処理を入れてみるのもよいと思います。

さて、ここで以下のような文書があったとします。1行が1レコードです。

"Toyota", "ハイブリッドカーを作っています。"
"Toyota", "ハイブリッドカーを売っています。"
"Toyota", "自動車を作っています。"
"Toyota", "自動車を売っています。"
"Nissan", "EVを作っています。"
"Nissan", "EVを売っています。"
"Nissan", "自動車を売っています。"
"Nissan", "ルノーと提携しています。"
"Nissan", "軽自動車を売っています。"
"Honda", "自動車を作っています。"
"Honda", "自動車を売っています。"
"Honda", "バイクを作っています。"
"Honda", "バイクを売っています。"
"Honda", "軽自動車を売っています。"
"Honda", "軽自動車を作っています。"

文書を「Toyota」「Nissan」「Honda」に分けて考えたとき、「特徴的なキーワード」は何でしょうか?
特徴的なキーワードをNLP4Jを使って出してみます。(難しい処理はしていません)
統計処理を「SimpleDocumentIndex」クラスを使って行っているところがポイントです。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

public class HelloTextMiningMain1 {
    public static void main(String[] args) throws Exception {
// ドキュメントの用意(CSVを読み込むなどでも可)
        List<Document> docs = new ArrayList<Document>();
        {
            docs.add(createDocument("Toyota", "ハイブリッドカーを作っています。"));
            docs.add(createDocument("Toyota", "ハイブリッドカーを売っています。"));
            docs.add(createDocument("Toyota", "自動車を作っています。"));
            docs.add(createDocument("Toyota", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "EVを作っています。"));
            docs.add(createDocument("Nissan", "EVを売っています。"));
            docs.add(createDocument("Nissan", "自動車を売っています。"));
            docs.add(createDocument("Nissan", "ルノーと提携しています。"));
            docs.add(createDocument("Nissan", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "自動車を作っています。"));
            docs.add(createDocument("Honda", "自動車を売っています。"));
            docs.add(createDocument("Honda", "バイクを作っています。"));
            docs.add(createDocument("Honda", "バイクを売っています。"));
            docs.add(createDocument("Honda", "軽自動車を売っています。"));
            docs.add(createDocument("Honda", "軽自動車を作っています。"));
        }

// 形態素解析アノテーター
        DocumentAnnotator annotator = new YJpMaAnnotator();
// 形態素解析処理
        annotator.annotate(docs);

// キーワードインデックス(統計処理)の用意
        Index index = new SimpleDocumentIndex();
// キーワードインデックス作成処理
        index.addDocuments(docs);
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Nissan");
            System.out.println("Keywords(名詞) for Nissan");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Toyota");
            System.out.println("Keywords(名詞) for Toyota");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
        {
            // 共起性の高いキーワードの取得
            List<Keyword> kwds = index.getKeywords("名詞", "item=Honda");
            System.out.println("Keywords(名詞) for Honda");
            for (Keyword kwd : kwds) {
                System.out.println(String.format("%.1f,%s", kwd.getCorrelation(), kwd.getLex()));
            }
        }
    }

    static Document createDocument(String item, String text) {
        Document doc = new DefaultDocument();
        doc.putAttribute("item", item);
        doc.setText(text);
        return doc;
    }

}

Output

Keywords(名詞) for Nissan
3.0,EV
3.0,ルノー
3.0,提携
1.0,軽自動車
0.6,自動車
Keywords(名詞) for Toyota
3.8,ハイブリッド
3.8,カー
1.5,自動車
Keywords(名詞) for Honda
2.5,バイク
1.7,軽自動車
1.0,自動車

簡単ですね!
このような結果になりました!
人間の感覚と合っているでしょうか?

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bean Validation 2.0 用での独自アノテーション実装とそのユニットテストのサンプルコード

概要

  • Bean Validation 2.0 を使用して独自に判定するアノテーションを作る
  • アノテーションではメールアドレスとして適切な文字列であるかを検証する

サンプルコード

ファイル一覧

  • Gradle 用の設定ファイル: build.gradle
  • Bean Validation 2.0 を使用するサンプルアプリケーション: App.java
  • メールアドレス文字列であるか検証するアノテーション: EmailAddress.java
  • ユニットテスト用クラス: EmailAddressTest.java
├── build.gradle
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── example
    │               ├── App.java
    │               └── annotation
    │                   └── EMailAddress.java
    └── test
        └── java
            └── com
                └── example
                    └── annotation
                        └── EMailAddressTest.java

build.gradle

Gradle 用の設定ファイル。
Bean Validation 2.0 を使用するためのライブラリと、テストに必要なライブラリを dependencies に記述している。

build.gradle
plugins {
  id 'java'
  id 'application'
}

group 'com.example'
version '1.0'

sourceCompatibility = 11

repositories {
  mavenCentral()
}

dependencies {

  // https://mvnrepository.com/artifact/javax.validation/validation-api
  implementation 'javax.validation:validation-api:2.0.1.Final'

  // https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
  runtimeOnly 'org.hibernate.validator:hibernate-validator:6.0.17.Final'

  // https://mvnrepository.com/artifact/org.glassfish/javax.el
  runtimeOnly 'org.glassfish:javax.el:3.0.1-b11'

  // https://mvnrepository.com/artifact/junit/junit
  testImplementation 'junit:junit:4.12'

  // https://mvnrepository.com/artifact/org.hamcrest/hamcrest-library
  testImplementation 'org.hamcrest:hamcrest-library:1.3'
}

test {
  testLogging {
    events 'passed', 'failed'
  }
}

application {
  mainClassName = 'com.example.App'
}

App.java

Bean Validation 2.0 を使用するサンプルアプリケーションのクラス。

package com.example;

import com.example.annotation.EMailAddress;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

public class App {

  public static void main(String[] args) {

    // Validator を取得
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();

    for (String arg : args) {
      // 入力データを用意
      InputData input = new InputData();
      input.setEMailAddress(arg);

      // バリデーションを実行
      Set<ConstraintViolation<InputData>> v = validator.validate(input);

      // 結果の確認
      if (v.size() == 0) {
        System.out.println(input.getEMailAddress() + ": メールアドレスとして適切です");
      } else {
        System.out.println(input.getEMailAddress() + ": " + v.iterator().next().getMessage());
      }
    }
  }

  private static class InputData {

    @EMailAddress(message = "メールアドレスとして適切ではありません", docomo = false)
    private String eMailAddress;

    // getter / setter
    public String getEMailAddress() { return eMailAddress; }
    public void setEMailAddress(String eMailAddress) { this.eMailAddress = eMailAddress; }
  }
}

EMailAddress.java

EMailAddress はメールアドレス文字列であるか検証するアノテーション。
実際の検証処理をするバリデータークラス EMailAddressValidator を内包している。
それぞれ Bean Validation 2.0 のアノテーションとバリデーションに必要なメソッド等を実装している。

package com.example.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.regex.Pattern;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Constraint(validatedBy = EMailAddress.EMailAddressValidator.class) // Bean Validation として扱う
@Documented // javadoc および同様のツールによってドキュメント化
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) // 適用可能な箇所
@Retention(RUNTIME) // コンパイラによってクラス・ファイルに記録され、実行時にVMによって保持される
public @interface EMailAddress {

  public static final String DEFAULT_MESSAGE = "not a well-formed email address";

  // Bean Validation として必要: エラーメッセージ
  String message() default DEFAULT_MESSAGE;

  // Bean Validation として必要: ターゲットグループをカスタマイズする用
  Class<?>[] groups() default {};

  // Bean Validation として必要: メタデータ情報の拡張用
  Class<? extends Payload>[] payload() default {};

  // 今回のアノテーション用に追加したメソッド
  // docomo の古いメールアドレスを許容するか否かを表す
  boolean docomo() default false;

  // ひとつの対象に複数の @EMailAddress を指定するための設定
  @Documented
  @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
  @Retention(RUNTIME)
  public @interface List {
    EMailAddress[] value();
  }

  // バリデーター。検証処理をするクラス。
  static class EMailAddressValidator implements ConstraintValidator<EMailAddress, CharSequence> {

    // HTML Standard の正規表現を使用
    // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
    private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");

    // アノテーションのインスタンス
    private EMailAddress constraintAnnotation;

    /**
     * バリデーターを初期化。
     * @param constraintAnnotation アノテーションのインスタンス
     */
    @Override
    public void initialize(EMailAddress constraintAnnotation) {
      this.constraintAnnotation = constraintAnnotation;
    }

    /**
     * 検証ロジックの実装。このメソッドは同時にアクセスされる可能性があるため、スレッドセーフにする必要がある。
     * @param value   検証するオブジェクト
     * @param context コンテキスト情報
     * @return オブジェクトがバリデーションに引っかかったら false
     */
    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {

      // null が渡された場合は true を返すことが推奨されている
      // 標準の @Email では空文字列が渡された場合に true を返しているのでそれに合わせる
      if (value == null || value.length() == 0) {
        return true;
      }

      // HTML Standard のメールアドレスの正規表現にマッチするか
      if (!PATTERN.matcher(value).matches()) {
        return false;
      }

      // 必要に応じて細かい判定を以下に追加
      // @EMailAddress アノテーションで条件を設定できるようにメソッドを定義しておけば
      // 利用側が細かい判定をON/OFF制御するように記述することも可能

      // たとえば docomo の古いメールアドレスを許容するか否か
      if (!constraintAnnotation.docomo()) {

        // 先頭の文字がピリオドだった
        if (value.charAt(0) == '.') {
          return false;
        }

        // @の前がピリオドだった
        if (value.toString().contains(".@")) {
          return false;
        }

        // ピリオドが2つ以上つながっていた
        if (value.toString().contains("..")) {
          // 必要に応じて、デフォルトの制約違反情報を破棄し、新しい情報を追加することもできる
          String newMessage = constraintAnnotation.message() + "(ダブルピリオドダメゼッタイ)";
          context.disableDefaultConstraintViolation();
          context.buildConstraintViolationWithTemplate(newMessage).addConstraintViolation();
          return false;
        }
      }

      // メールアドレスとして適切な文字列と判断
      return true;
    }
  }
}

EMailAddressTest.java

ユニットテスト用クラス。

package com.example.annotation;

import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.instanceOf;

// EMailAddress アノテーションをユニットテストするクラス
public class EMailAddressTest {

  // テストしやすいようにテスト用 Bean に統一したインターフェースを持たせる
  private interface TestBean {
    void setTestString(String testString);
  }

  // テスト用に Bean クラスを作成
  private static class NormalTestBean implements TestBean {

    @EMailAddress
    private String testString;

    NormalTestBean(String testString) { this.testString = testString; }

    // getter / setter
    public String getTestString() { return testString; }
    public void setTestString(String testString) { this.testString = testString; }
  }

  // テスト用に Bean クラスを作成
  private static class MessageTestBean implements TestBean {

    @EMailAddress(message = "メールアドレスとして適切ではありません")
    private String testString;

    MessageTestBean(String testString) { this.testString = testString; }

    // getter / setter
    public String getTestString() { return testString; }
    public void setTestString(String testString) { this.testString = testString; }
  }

  // テスト用に Bean クラスを作成
  private static class DocomoTestBean implements TestBean {

    @EMailAddress(docomo = true) // docomo の古いメールアドレスを許容する設定
    private String testString;

    DocomoTestBean(String testString) { this.testString = testString; }

    // getter / setter
    public String getTestString() { return testString; }
    public void setTestString(String testString) { this.testString = testString; }
  }

  // テストに使うバリデーター
  private static Validator validator;

  @BeforeClass
  public static void setUpBeforeClass() {
    // バリデーターを用意
    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    validator = validatorFactory.getValidator();
  }

  // メールアドレスとして適切な文字列をテストする
  @Test
  public void testValidEMailAddress() {
    assertValid(new NormalTestBean("abc@example.com"));
    assertValid(new NormalTestBean("abc+xyz@example.com"));
    assertValid(new NormalTestBean("abc.xyz@example.com"));
  }

  // メールアドレスとして適切でない文字列をテストする
  @Test
  public void testInvalidEMailAddress() {
    assertInvalid(new NormalTestBean("abc.example.com"), EMailAddress.DEFAULT_MESSAGE);
    assertInvalid(new NormalTestBean("a c.example.com"), EMailAddress.DEFAULT_MESSAGE);
    assertInvalid(new NormalTestBean("あいうえお@example.com"), EMailAddress.DEFAULT_MESSAGE);
    // 複数メールアドレス
    assertInvalid(new NormalTestBean("abc@example.com,xyz@example.com"), EMailAddress.DEFAULT_MESSAGE);
    assertInvalid(new NormalTestBean("abc@example.com xyz@example.com"), EMailAddress.DEFAULT_MESSAGE);
    // 以前NTTドコモの携帯電話用メアドで許可されていたRFC違反の形式
    assertInvalid(new NormalTestBean(".abc@example.com"), EMailAddress.DEFAULT_MESSAGE); // 先頭にピリオド
    assertInvalid(new NormalTestBean("abc.@example.com"), EMailAddress.DEFAULT_MESSAGE); // @の直前にピリオド
    assertInvalid(new NormalTestBean("abc..xyz@example.com"), EMailAddress.DEFAULT_MESSAGE + "(ダブルピリオドダメゼッタイ)"); // ピリオド2つ以上連続
  }

  // メッセージが置き換わっていることをテストする
  @Test
  public void testMessage() {
    assertInvalid(new MessageTestBean("abc.example.com"), "メールアドレスとして適切ではありません");
  }

  // docomo の古いメールアドレスを許容できていることをテストする
  @Test
  public void testDocomo() {
    assertValid(new DocomoTestBean(".abc@example.com"));
    assertValid(new DocomoTestBean("abc.@example.com"));
    assertValid(new DocomoTestBean("abc..xyz@example.com"));
  }

  // バリデーションOKタイプ
  private void assertValid(TestBean bean) {
    Set<ConstraintViolation<TestBean>> violations = validator.validate(bean);
    assertThat(violations, is(empty())); // バリデーションに引っかからなければOK
  }

  // バリデーションNGタイプ
  private void assertInvalid(TestBean bean, String expectedMessage) {
    Set<ConstraintViolation<TestBean>> violations = validator.validate(bean);
    // バリデーションに引っかかったかどうか
    assertThat(violations, is(not(empty())));
    // 引っかかったバリデーションが EMailAddress であるか
    ConstraintViolation<TestBean> violation = violations.iterator().next();
    assertThat(violation.getConstraintDescriptor().getAnnotation(), is(instanceOf(EMailAddress.class)));
    // メッセージが一致しているか
    assertThat(violation.getMessage(), is(expectedMessage));
  }
}

ユニットテストを実行する

$ gradle test

> Task :test

com.example.annotation.EMailAddressTest > testInvalidEMailAddress PASSED

com.example.annotation.EMailAddressTest > testValidEMailAddress PASSED

com.example.annotation.EMailAddressTest > testMessage PASSED

com.example.annotation.EMailAddressTest > testDocomo PASSED

BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed

サンプルアプリケーションを実行する

$ gradle run --args="abc@example.com .abc@example.com a..bc@example.com"

> Task :run
10月 25, 2019 8:28:41 午後 org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 6.0.17.Final
abc@example.com: メールアドレスとして適切です
.abc@example.com: メールアドレスとして適切ではありません
a..bc@example.com: メールアドレスとして適切ではありません(ダブルピリオドダメゼッタイ)

BUILD SUCCESSFUL in 1s
2 actionable tasks: 1 executed, 1 up-to-date

参考資料

ConstraintValidator の isValid について

ConstraintValidator の isValid に null が渡された場合は true を返すことが推奨されている。null の検証は @NotNull で行う。

Bean Validation specification - 3.4. Constraint validation implementation

While not mandatory, it is considered a good practice to split the core constraint validation from the not null constraint validation (for example, an @Email constraint will return true on a null object, i.e. will not take care of the @NotNull validation).

null can have multiple meanings but is commonly used to express that a value does not make sense, is not available or is simply unknown. Those constraints on the value are orthogonal in most cases to other constraints. For example a String, if present, must be an email but can be null. Separating both concerns is a good practice.

アノテーションとバリデーターの実装の参考

Hibernate Validator の実装が参考になる。
org.hibernate.validator.internal.constraintvalidators パッケージの下には bv パッケージと hv パッケージが用意されている。bv は Bean Validation の略で hv は Hybernate Validation の略だと思われる。

Bean Validation 公式とそれに準ずる資料

Bean Validation 実装に関連するAPIリファレンス資料

Bean Validation 実装の参考資料

Bean Validation ユニットテストの参考資料

メールアドレス仕様の参考資料

HTML Standard に記載されているメールアドレスの正規表現。RFC 的に完全ではないがある程度はこれでチェックできる。

HTML Standard - 4.10.5.1.5 E-mail state (type=email)

The following JavaScript- and Perl-compatible regular expression is an implementation of the above definition.

/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/

docomo の携帯電話ではピリオドが連続するメールアドレスを認めていたことがある。

メールアドレス - Wikipedia

ローカル部にはquoted-string形式でなければ“.”を先頭と末尾で使用することや2個以上連続して使用することはできない。 しかし、一部の実装(実例:携帯電話のメール)はこの仕様を逸脱しており、規定外の特殊な文字が使用可能な場合もある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

公開済

NLP4J [001] Javaで形態素解析をしてみる
NLP4J [002] Javaで日本語の構文解析をしてみる
NLP4J [003] Javaで自然言語処理と品詞の統計処理を使ってテキスト分析をしてみる
NLP4J [004] Javaで自然言語処理と構文解析の統計処理を使ってテキスト分析をしてみる

公開予定

NLP4J のご紹介 - [00N] Javaで形態素解析と構文解析を使って何をするか

NLP4J のご紹介 - [00N] Keywordクラスについて説明

NLP4J のご紹介 - [00N] Javadoc

NLP4J のご紹介 - [00N] Twitter4J と NLP4J でTwitter分析をしてみる

NLP4J のご紹介 - [00N] Azure と NLP4J でテキスト分析システムを作ってみる

NLP4J のご紹介 - [-001]ドメイン名 nlp4j.org の取得

NLP4J のご紹介 - [-002] Maven Central Repository への登録

否定形の話

自然言語ソリューション事例の話

COTOHA APIを試す...かも
https://api.ce-cotoha.com/home
https://api.ce-cotoha.com/contents/reference.html

完璧な辞書など存在しない

日本でもっとも多く使われている日本語形態素解析エンジンは?

Yahoo!デベロッパーネットワーク テキスト解析:日本語形態素解析 について

企業向け辞書の階層の作り方

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J [002] Javaで日本語の構文解析をしてみる

Indexに戻る : [001]形態素解析 > [002]構文解析 > [003]品詞の統計処理

NLP4J を使ってJavaで日本語の構文解析(係り受け解析)をしてみます。

構文解析ができると「車が急に止まった」というテキストから
・「車が→止まった」
・「急に→止まった」
というような情報を抽出することができます。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

import nlp4j.Keyword;
import nlp4j.KeywordWithDependency;
import nlp4j.impl.DefaultNlpServiceResponse;
import nlp4j.yhoo_jp.YJpDaService;
public class HelloNLP4JDA {
        // 自然文のテキスト
        String text = "車が急に止まった。";
        // 係り受け解析
        YJpDaService service = new YJpDaService();
        // 係り受け解析の結果を取得する
        DefaultNlpServiceResponse response = service.process(text);
        for (Keyword kwd : response.getKeywords()) {
            if (kwd instanceof KeywordWithDependency) {
                // 係り受け解析の結果を出力する
                System.err.println(((KeywordWithDependency) kwd).toStringAsDependencyTree());
            }
        }
}

Output1

このような感じで出力できます。簡単ですね!

-sequence=6,lex=null,str=。
    -sequence=5,lex=null,str=た
        -sequence=4,lex=null,str=止まっ
            -sequence=2,lex=null,str=が
                -sequence=1,lex=null,str=車
            -sequence=3,lex=null,str=急に

Code2

キーワードのオブジェクトを操作することで細かい結果を調べることができます。

    public static void main(String[] args) throws Exception {
        // 自然文のテキスト
        String text = "車が急に止まった。";
        // 係り受け解析
        YJpDaService service = new YJpDaService();
        // 係り受け解析の結果を取得する
        DefaultNlpServiceResponse response = service.process(text);
        for (Keyword kwd : response.getKeywords()) {
            if (kwd instanceof KeywordWithDependency) {
                // 係り受け解析の結果を出力する
                print((KeywordWithDependency) kwd, 0);
            }
        }
    }

    static void print(KeywordWithDependency kwd, int depth) {
        System.err.println(depth + ":" + kwd.getStr());
        for (KeywordWithDependency kwd2 : kwd.getChildren()) {
            print(kwd2, depth + 1);
        }
    }

Output2

0:。
1:た
2:止まっ
3:が
4:車
3:急に

簡単ですね!

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NLP4J のご紹介 - [002] Javaで日本語の構文解析をしてみる

Indexに戻る
[001]形態素解析 > [002]構文解析 > [003]品詞の統計処理

NLP4J を使ってJavaで日本語の構文解析(係り受け解析)をしてみます。

構文解析ができると「車が急に止まった」というテキストから
・「車が→止まった」
・「急に→止まった」
というような情報を抽出することができます。

Maven

<dependency>
  <groupId>org.nlp4j</groupId>
  <artifactId>nlp4j</artifactId>
  <version>1.0.0.0</version>
</dependency>

Code1

import nlp4j.Keyword;
import nlp4j.KeywordWithDependency;
import nlp4j.impl.DefaultNlpServiceResponse;
import nlp4j.yhoo_jp.YJpDaService;
public class HelloNLP4JDA {
        // 自然文のテキスト
        String text = "車が急に止まった。";
        // 係り受け解析
        YJpDaService service = new YJpDaService();
        // 係り受け解析の結果を取得する
        DefaultNlpServiceResponse response = service.process(text);
        for (Keyword kwd : response.getKeywords()) {
            if (kwd instanceof KeywordWithDependency) {
                // 係り受け解析の結果を出力する
                System.err.println(((KeywordWithDependency) kwd).toStringAsDependencyTree());
            }
        }
}

Output1

このような感じで出力できます。簡単ですね!

-sequence=6,lex=null,str=。
    -sequence=5,lex=null,str=た
        -sequence=4,lex=null,str=止まっ
            -sequence=2,lex=null,str=が
                -sequence=1,lex=null,str=車
            -sequence=3,lex=null,str=急に

Code2

キーワードのオブジェクトを操作することで細かい結果を調べることができます。

    public static void main(String[] args) throws Exception {
        // 自然文のテキスト
        String text = "車が急に止まった。";
        // 係り受け解析
        YJpDaService service = new YJpDaService();
        // 係り受け解析の結果を取得する
        DefaultNlpServiceResponse response = service.process(text);
        for (Keyword kwd : response.getKeywords()) {
            if (kwd instanceof KeywordWithDependency) {
                // 係り受け解析の結果を出力する
                print((KeywordWithDependency) kwd, 0);
            }
        }
    }

    static void print(KeywordWithDependency kwd, int depth) {
        System.err.println(depth + ":" + kwd.getStr());
        for (KeywordWithDependency kwd2 : kwd.getChildren()) {
            print(kwd2, depth + 1);
        }
    }

Output2

0:。
1:た
2:止まっ
3:が
4:車
3:急に

簡単ですね!

Indexに戻る

NLP4J のご紹介 - [000] Javaで自然言語処理 Index

プロジェクトURL

https://www.nlp4j.org/
NLP4J_N_128.png


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リファクタリングしました(Lombokの利用、Spring JPAの利用、ラムダ式で例外ラッピング)

はじめに

Spring-boot+Heroku+PostgreSQL+Flywayでアプリ作成してみた」の続きです。
前からやろうとしていたリファクタしました。

それぞれが小さい修正なので、まとめて記事にしました。

ターゲット

こちらの内容で気になるポイントがある方。
気になるポイントだけチェックしてもらえればと思います。

  1. Lombokの利用
  2. iBatisからSpring-data-jpaへの変更
  3. ラムダ式で例外ラッピング

1. Lombokの利用

Lombok導入方法(IDEの設定)と、利用内容を記載します。

Lombok導入

以下2つを実施します。

  1. Preference>Plugins でLombokをインストール
  2. Build,Execution,Deployment>Compiler>Annotation Processors でEnable annotation processingをチェックする

※ ググったら以下のように同じ内容が出てくるので、ここはそんなに悩まないと思います。
https://reasonable-code.com/intellij-lombok/

Lombokの利用

こちらがLombok対応のコミットです。
以下で1つずつ説明していきますが、一部抜粋なので詳細を知りたい方はこちらをご確認ください。
https://github.com/ken-araki/line_bot/commit/68198b7f8005a599d97d243382991529f9a51365

build.gradle

build.gradle
dependencies {
    // 以下2つを追加する
    compileOnly 'org.projectlombok:lombok:1.18.10'
    annotationProcessor 'org.projectlombok:lombok:1.18.10'

Lombokを依存関係に追加します。
注意点としては、Gradle5以降はannotationProcessorを記載しないとコンパイルエラーがおきます。
※ Gradle4までならIDEの設定ができていれば問題ないです。

Data

TrainDelay.java
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TrainDelay {
    private String name;
    private String company;
    private String lastupdate_gmt;
    private String source;
}

DTOのgetter/setterを削除して、@Dataをつけました。
これで、@Getter/@Setter @ToString @EqualsAndHashCode @RequiredArgsConstructorと同じになります。
※ getter/setterメソッド、toString()/hashCode()の実装、finalメンバーのコンストラクタが提供されます。

今回はありませんが、ReadOnlyのデータがある場合はfinalをつけてください。
そうすればsetterが提供されなくなります。

Controller, Service

NoticeGarbageOutController.java
@Slf4j
@AllArgsConstructor
@Controller
public class NoticeGarbageOutController {
    private LineMessagingClient lineMessagingClient;

    // 省略...

全てConstructor Injectionへ変更しました。
自分で書いていたコンストラクタを削除して、@AllArgsConstructorをつけるだけでOKです。

ただし、コンストラクタを複数定義すると正しく動作しません。
NullPointerExceptionが起きる(DI対象がNull)時は複数コンストラクタを実装していないかチェックしてください。

これでコツコツ@Autowiredを書かなくてもメンバー定義だけでDIしてくれます。

2. iBatisからSpring-data-jpaへの変更

業務で使っているのでiBatisから入りましたが、基本的にJPA推しなのでのせかえました。
ここでは、JPA導入と簡単なCRUDを紹介します。
※ iBatisのほうが柔軟だと思いますが、JPAでシンプルに作っていくことでシステムが複雑化しづらいと思ってます。

Spring-data-jpaの導入

最初にソースdiffをのせておきます。
iBatisの削除も同じコミットにあります。
※ postgresql前提です

https://github.com/ken-araki/line_bot/commit/2931a3b2b2b37d964725beca05641b5f9861da64

build.gradle

build.gradle
dependencies {
    // 以下2つを追加
    implementation 'org.postgresql:postgresql:42.2.8'
    runtime 'org.postgresql:postgresql:42.2.8'

boot:spring-boot-starter-data-jpapostgresqlを依存関係に追加するだけです。

Entity

Tobuy.java
@Data
@Entity
@Table(name = "tobuy")
@NoArgsConstructor
@AllArgsConstructor
public class Tobuy {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "id")
    private String id;
    @Column(name = "goods")
    private String goods;
    @Column(name = "is_completed")
    private String isCompleted;
    @Column(name = "created_date")
    private Date createdDate;
    @Column(name = "updated_date")
    private Date updatedDate;
}

@EntityでこのclassがEntityであること、
@Table, @Columnで、DBとのマッピングを行います。
1テーブルだけなのでなんとなく分かると思います。

@Id@GeneratedValue(strategy= GenerationType.IDENTITY)で主キーを表しています。
詳細はこちらを参照してください。

Repository

TobuyRepository.java
@Repository
public interface TobuyRepository extends JpaRepository<Tobuy, Long> {
    List<Tobuy> findByIsCompleted(String isCompleted);

    @Modifying
    @Query(
            value = "update tobuy set is_completed = '1', updated_date = current_timestamp where is_completed = '0';",
            nativeQuery = true
    )
    int updateAllCompleted();
}

JpaRepositoryを継承するだけでだいたいのことができます。
全検索のfindAll()、追加更新のsave()は自動で実装されています。
条件(where句)付きのselectは定義を書くだけで実装してくれます。
今回で言えばfindByIsCompletedwhere is_completed = 'X'条件のselect文を発行します。

update文は@Queryを利用し自分で書きました。
もっといい方法がありそうですが見つけられず。。。もう少し模索しようと思っています。
更新SQLを実装するときは@Modifyingを忘れずに。忘れるとSpring起動時にエラーがおきます。

設定周り

application.properties
spring.datasource.url = jdbc:postgresql://localhost:5432/testdb
spring.datasource.username = postgres
spring.datasource.password = postgres1234
spring.datasource.driver-class-name = org.postgresql.Driver

jdbcの設定も必要です。
※ iBatis時点で記載済みなのでcommitには乗っていません。
※ DB設定を各自合わせるか前回の記事を参考にDBを用意してください。

hibernate.properties
hibernate.show_sql = true
hibernate.jdbc.lob.non_contextual_creation = true

また、posgresqlを利用している場合、以下設定が必要です。
これがないとPgConnection.createClob() メソッドはまだ実装されていません。というエラーがおきます。
hibernate.show_sql = trueはSQLをログ出力するだけなので無くても大丈夫です。

3. ラムダ式で例外ラッピング

RuntimeExceptionにラップするだけの処理が多くなりそうだったので、
Exceptionをラップする共通関数を用意しました。

修正点

diffです。
他の修正も入っていますが、そっと読み飛ばしてください。

https://github.com/ken-araki/line_bot/commit/1096a266edfc01f7bc258258c786b932176a2129

Utils

Utils.java
// 一部抜粋
public class Utils {
    public static <T, E extends Exception> T uncheck(Supplier_WithExceptions<T, E> supplier) {
        try {
            return supplier.get();
        } catch(Throwable e) {
            throw new RuntimeException(e);
        }
    }
    // 省略...
    @FunctionalInterface
    public interface Supplier_WithExceptions<T, E extends Exception> {
        T get() throws E;
    }
    // 省略...
}

Exceptionをスローする関数型インターフェースを定義するだけです。
それを各関数ごとにオーバーロードすれば、利用者側は何も気にせずUtils.uncheck(() -> {/** 実行したい処理 **/}) という感じで利用します。

こちらを参考にさせてもらいました。

終わりに

少しずつですが、コツコツ進めております。
継続してこんな規模感で記事を書いていこうと思います。

次はこんなことをやろうと思っています。

  • kotlin化 (せっかくなのでもう少しjavaロジックが充実してからやろうと思っています。)
  • JUnit
  • LINE Message API のローカルテスト(割と難しい気がしてきた)
  • Service周りのリファクタ
  • 新しい機能追加

現時点のソース

ちなみに、現時点ではこんな感じです。

https://github.com/ken-araki/line_bot

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む