- 投稿日:2019-10-26T21:46:55+09:00
[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
- 投稿日:2019-10-26T21:46:55+09:00
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
- 投稿日:2019-10-26T21:46:55+09:00
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
- 投稿日:2019-10-26T19:05:40+09:00
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
- 投稿日:2019-10-26T19:05:40+09:00
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
- 投稿日:2019-10-26T16:11:14+09:00
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.javabuild.gradle
Gradle 用の設定ファイル。
Bean Validation 2.0 を使用するためのライブラリと、テストに必要なライブラリを dependencies に記述している。build.gradleplugins { 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 の略だと思われる。
- hibernate-validator/AbstractEmailValidator.java at 6.0.17.Final · hibernate/hibernate-validator · GitHub
- hibernate-validator/EmailValidator.java at 6.0.17.Final · hibernate/hibernate-validator · GitHub
- hibernate-validator/NotEmptyValidatorForCharSequence.java at 6.0.17.Final · hibernate/hibernate-validator · GitHub
Bean Validation 公式とそれに準ずる資料
- Bean Validation - Bean Validation 2.0 (JSR 380)
- Bean Validation specification
- The Bean Validation reference implementation. - Hibernate Validator
- Getting started with Hibernate Validator - Hibernate Validator
Bean Validation 実装に関連するAPIリファレンス資料
- Overview (Jakarta Bean Validation API 2.0.2)
- Constraint (Jakarta Bean Validation API 2.0.2)
- ConstraintValidator (Jakarta Bean Validation API 2.0.2)
- java.lang.annotation (Java Platform SE 8 )
Bean Validation 実装の参考資料
- 4.1. 入力チェック — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.5.1.RELEASE documentation
- Spring カスタムアノテーションに出会った話 - Qiita
- JavaEE使い方メモ(Bean Validation) - Qiita
- Bean Validationのアノテーションに付いてるListについて - CLOVER?
Bean Validation ユニットテストの参考資料
- 10.2.3. 機能ごとのテスト実装 — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.5.1.RELEASE documentation
- Spring 単項目バリデーションのテスト - Qiita
メールアドレス仕様の参考資料
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 の携帯電話ではピリオドが連続するメールアドレスを認めていたことがある。
ローカル部にはquoted-string形式でなければ“.”を先頭と末尾で使用することや2個以上連続して使用することはできない。 しかし、一部の実装(実例:携帯電話のメール)はこの仕様を逸脱しており、規定外の特殊な文字が使用可能な場合もある。
- 投稿日:2019-10-26T11:47:32+09:00
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
- 投稿日:2019-10-26T08:29:07+09:00
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
- 投稿日:2019-10-26T08:29:07+09:00
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
- 投稿日:2019-10-26T02:29:25+09:00
リファクタリングしました(Lombokの利用、Spring JPAの利用、ラムダ式で例外ラッピング)
はじめに
「Spring-boot+Heroku+PostgreSQL+Flywayでアプリ作成してみた」の続きです。
前からやろうとしていたリファクタしました。それぞれが小さい修正なので、まとめて記事にしました。
ターゲット
こちらの内容で気になるポイントがある方。
気になるポイントだけチェックしてもらえればと思います。1. Lombokの利用
Lombok導入方法(IDEの設定)と、利用内容を記載します。
Lombok導入
以下2つを実施します。
- Preference>Plugins で
Lombokをインストール- 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/68198b7f8005a599d97d243382991529f9a51365build.gradle
build.gradledependencies { // 以下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.gradledependencies { // 以下2つを追加 implementation 'org.postgresql:postgresql:42.2.8' runtime 'org.postgresql:postgresql:42.2.8'
boot:spring-boot-starter-data-jpaとpostgresqlを依存関係に追加するだけです。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は定義を書くだけで実装してくれます。
今回で言えばfindByIsCompletedはwhere is_completed = 'X'条件のselect文を発行します。update文は
@Queryを利用し自分で書きました。
もっといい方法がありそうですが見つけられず。。。もう少し模索しようと思っています。
※ 更新SQLを実装するときは@Modifyingを忘れずに。忘れるとSpring起動時にエラーがおきます。設定周り
application.propertiesspring.datasource.url = jdbc:postgresql://localhost:5432/testdb spring.datasource.username = postgres spring.datasource.password = postgres1234 spring.datasource.driver-class-name = org.postgresql.Driverjdbcの設定も必要です。
※ iBatis時点で記載済みなのでcommitには乗っていません。
※ DB設定を各自合わせるか前回の記事を参考にDBを用意してください。hibernate.propertieshibernate.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周りのリファクタ
- 新しい機能追加
現時点のソース
ちなみに、現時点ではこんな感じです。
