Google Natural Language API使って自然言語処理して内部リンク自動生成してみた。

Google Natural Language API使って自然言語処理して内部リンク自動生成してみた。

Cloud Natural Languageは、Googleの機械学習を使った自然言語処理API
MeCabのようないわゆる形態素解析ではなく、
Entity分析やSentence分析など、ある程度Google側で解析した結果を返却してくれるのが特徴
沢山文章を送ると有料になりますが、大規模サービスで無ければ基本的には無料の範囲で使うことが可能。

Google Natural Language APIとは

Google Natural Languageとは、
公式サイトによると、

Google の機械学習を使って、非構造化テキストから分析情報を得ることができます。

とのこと。

具体的な使用例として、

  • 顧客から得られる分析情報
  • マルチメディアと多言語のサポート
  • コンテンツの分類と関係グラフ
  • Google のディープ ラーニング モデル

といったものが挙げられています。(他のことにも使えると思います。)

今回は、さっくり使用方法についての解説と、
これを使って、Wikipediaへのリンクを自動的に生成する仕組みを作ってみたいと思います。

デモを使ってどんな結果が出るか確かめる。

まずは、どんな結果を返してくれるのか、公式サイトのデモを使ってみましょう。
TryTheApi
上記のように、文章を入力して「Analyze」をクリック。
すると下に色々結果が出てきました。APIを実際にコンソール上で実行する際には、
これらのどの結果を出すかこちらで指定しますが、デモの時は全部出してくれます。
具体的には以下のような項目になります。

  • Entities(エンティティ分析)
  • Sentiment(感情分析)
  • Syntax(構文解析)
  • Categories

以下で細かく解説していきます。

Entities(エンティティ分析)

恐らく一番MeCab等のイメージに近い分析をしてくれます。
文章を分析して、Entity、つまり自立した名詞を取ってきてくれます。
「その名詞が固有名詞であるか、一般名詞であるか」「文章の中でどのくらい重要な単語(キーワード)であるか」「名詞の種類(地名であるか、人名であるか等)」といったものを表示してくれています。
また、ここで今回の記事で大事なのが、一部の単語については、Wikipediaへのリンクを表示してくれるということです。

API実行結果
上記のようになっています。
この文章の場合、「蒼井そら」さんへのリンクは張らない方がいいですが、Twitterについては内部リンクが張ってあれば、
Twitterって何だろう?と思った人がWikipediaを見て調べることが出来ます。

今回はそれをやっていきたいと思っているので、このEntities分析を使っていくことになります。

Sentiment(感情分析)

この分析では、感情がポジティブかネガティブかのスコアを付けてくれます
Amazonレビューや、お客様アンケートの結果などの全体的なポジティブ・ネガティブの分析をする場合などに使えそう。
数値の見方は簡単で、Scoreがプラスならポジティブ、マイナスならネガティブと考えればOKです。
このサイトがもっと人気になってコメントとかつくようになったり、アプリのレビューとかが付くようになったら使ってみて記事にしたいと思います。
(いつになるのか・・・)

Syntax(構文解析)

これは係り受け(どの言葉がどの言葉にかかってるか、修飾してるかという分析のこと)の解析とかをやってくれてます。
結構専門的なところで、活用するにも知識がいる分野だと思うので割愛しますが、
より正確な構文解析をするのであれば、係り受けは重要な分野です。
ただ、これも確かOpenNLPとかで機械学習で良い感じにできたので、わざわざ自分で実装する必要はないのかなと思います。
使うとしたら、ここで係り受け解析したものをOpenNLPに食わせるとかでしょうか、ちょっとパッとは浮かびませんでした。

Categories?

ここは自分がクリックしたときは使えませんでした。恐らく日本語非対応?

早速APIを使っていく。

APIの利用をするにはクレジットカードの申し込みとか色々必要です。
無料トライアル期間を使えば、終了してから勝手にクレジットカードからお金を請求されることはないので、
そちらを活用してから切り替えるか考えましょう。

申し込みをすると、勝手にGCPのダッシュボードまで遷移してくれるので、
そこで、「APIの有効化」を探してクリックし、「APIの有効化」をクリック。
検索窓に「natural language」と入れると以下の様にAPIが出てくるのでクリックして有効にしましょう。
APIの有効化画面

その後、認証情報を作成する必要があるので、手順に従って作成し、JsonファイルをDLしましょう。

そしたら、こちらのクイックスタートに従って進めていきます。
諸々の手続きはそちらが詳しいので、詰まりポイントだけ。

GOOGLE_APPLICATION_CREDENTIALSの設定

これ、ターミナルでやってる分にはクイックスタートの方法で問題ないのですが、
eclipseとかでやるときは一工夫入れないとだめです。
自分はeclipseの実行の構成の編集とか久々にやったので詰まりました・・・。
更に言うと、上の手順で作ったcredientialもプロジェクト内に入れないとだめなのですが、
これって何処に入れるのが正しいのでしょうか?

課金が発生するので、あんまりセキュリティ考えずに置くと怒られるポイントだと思いますが・・・。
とりあえず、一旦無料トライアルなので気にせず、
src/main/配下にconfigディレクトリを切り、その下にDLしたjsonファイルを配置しました。

このまま実行すると、以下のようなエラーが出てきます。

 The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

なんか色々言ってますが、とにかくデフォルトの認証は使えないぞ、GOOGLE_APPLICATION_CREDENTIALSにちゃんと認証情報のパス指定しろということを言ってます。

なので、実行→実行の構成をクリック

実行の構成選択

以下の画面が出てくるので、「環境」タブに合わせて新規環境変数を追加し、
変数名を「GOOGLE_APPLICATION_CREDENTIALS 」に、値を認証情報の入ったjsonファイルの配置箇所にしてあげましょう。

実行時の構成編集

これで実行すれば、以下のような結果が出てきます。

Text: Hello, world!
Sentiment: 0.3, 0.3

Hello,Worldの重要性は各単語同じだそうです。

内部リンクを自動生成する。

そして本番、wikilinkの自動生成を行います。
実際に利用する文章は、試しに以下のような文章とします。
「松岡修三がバンクーバーに行ってしまったので、日本は今太陽神が不在で気温が非常に下がってしまっています。」
バンクーバー、日本、上手くいけば松岡修造太陽神あたりがWikipediaリンクを張れそうです。

実際にやってみた結果が以下の様になります。

※リンク付き
松岡修三バンクーバーに行ってしまったので、日本今太陽神が不在で気温が非常に下がってしまっています。
※リンク可視化
<a href=”https://en.wikipedia.org/wiki/Shuzo_Matsuoka”>松岡修三</a>が<a href=”https://en.wikipedia.org/wiki/Vancouver”>バンクーバー</a>に行ってしまったので、<a href=”https://en.wikipedia.org/wiki/Japan”>日本</a>は<a href=”https://en.wikipedia.org/wiki/Solar_deity”>今太陽神</a>が不在で気温が非常に下がってしまっています。

狙い通りの場所にアンカータグが付いています!
今、太陽神が、と言う意味合いの文章のところが、「今太陽神」で取られてしまっているのは気になりますが、まぁプロトタイプはこんなものでしょう。

実装したコードは以下

import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.google.cloud.language.v1.AnalyzeEntitiesResponse;
import com.google.cloud.language.v1.Document;
import com.google.cloud.language.v1.Document.Type;
import com.google.cloud.language.v1.EncodingType;
import com.google.cloud.language.v1.Entity;
import com.google.cloud.language.v1.EntityMention;
import com.google.cloud.language.v1.LanguageServiceClient;

public class App {
	public static void main(String[] args) throws IOException {
		try (LanguageServiceClient language = LanguageServiceClient.create()) {

			// The text to analyze
			String text = "松岡修三がバンクーバーに行ってしまったので、日本は今太陽神が不在で気温が非常に下がってしまっています。";
			Document doc = Document.newBuilder()
					.setContent(text).setType(Type.PLAIN_TEXT).build();

			// Detects the sentiment of the text
			AnalyzeEntitiesResponse res = language.analyzeEntities(doc, EncodingType.UTF16); //APIにテキストとエンコードタイプセット

			List entityList = res.getEntitiesList(); // APIのレスポンス

			Map<Integer, String[]> offsetWordsMap = new TreeMap<>(new Comparator() {
				public int compare(Integer m, Integer n) {
					return ((Integer) m).compareTo(n) * -1;
				}
			}); //降順ソートしたいのでTreeMap

			for (Entity entity : entityList) {
				Map<String, String> map = entity.getMetadataMap(); //メタデータマップを取得
				String wikiUrl = map.get("wikipedia_url"); //wikipedia_urlをキーにurlを取得

				if (wikiUrl != null) {
					List mentionList = entity.getMentionsList(); //単語出現位置(offset)を取得

					EntityMention mention = mentionList.get(0); //SEO的には重複しない方が好ましいので一個目だけ
					String[] tmp = new String[2];
					tmp[0] = entity.getName(); //エンティティ(単語)をセット
					tmp[1] = wikiUrl; //wikilinkセット
					offsetWordsMap.put(mention.getText().getBeginOffset(), tmp); // offsetをkeyにword, wikilinkの配列をセット
				}
			}

			//基の文章に後ろからアンカータグ追加
			StringBuilder sb = new StringBuilder(text);

			for (Entry<Integer, String[]> entry : offsetWordsMap.entrySet()) {
				sb.insert(entry.getKey() + entry.getValue()[0].length(), "</a>");
				String ancherStr = "<a href=\""+ entry.getValue()[1] + "\">";
				sb.insert(entry.getKey(), ancherStr);
			}

			System.out.println(sb);

		}
	}
}

細かい所はコメントに書いてあるのでざっくりと。
まずAPIをたたいてレスポンスを受け取り
そのレスポンスから必要な情報であるワードの最初の出現位置(offset)、そのワードそのもの、そしてwikilinkを取ってきます
降順に並べないと、アンカータグを追加するのが面倒なので降順に並べてoffsetをkeyにしています。

そのあとは降順に並べたものをforでぶん回しして、
後ろからアンカータグを追加していきます。
(※前から並べると、どんどんoffset位置がずれていくので計算が面倒になる。)

textにべた書きしてますが、実際は標準出力に持たせるとか、
ファイルを何処かに入れて実行すると結果のテキストファイルを出力するとかそんな感じにすると楽かなと思います。

というわけで、たったこれだけで自動で内部リンクを生成してくれました。
実際には、wikilinkが英語だったりするので修正が必要ですが、その辺は細かいので追々調整していきます。

まとめ

というわけで、Google Natural Language APIを使って内部リンクを自動生成してみました。
ブログのSEO対策に困っている方等は参考になれば幸いです。
もっとスマートなやり方や、精度を上げる方法を知っている人がいたらコメントや問い合わせからこっそり教えて頂けたら嬉しいです。