Word2Vec(2)distance.js

JavaScript implementation of Word2Vec


2021/01/27
藤田昭人


本稿は 前回 の続編です。

Googleのオリジナル実装を使うと比較的お手軽に Word2Vec が使えることがわかりました。が、 BookBotJavaScript 専用 PaaS である Glitch で動いているので(コーパスの学習は word2vec コマンドを使うとしても)word2vec の ご利益を得るには、 学習済みデータへアクセスする JavaScript のコードが必要になります。

で、最近では JavaScript にも慣れてきたので 「サクッと作れるだろう…」と 当初は甘くみていたのですが、さにあらず。 UNIXプログラミングでは 使い慣れてたはずのストリームの扱いに ハマるハマる。 本稿ではそのドタバタの結果を 紹介したいと思います。


学習済みデータが取り込めない

まずは nodejs の常套句のような次の2行で、前回紹介した 東北大学の日本語 word2vec 学習済みデータ を取り込もうとしたのですが…

const fs = require('fs');
var buf = fs.readFileSync('entity_vector/entity_vector.model.txt', 'utf8');

これを nodejs で実行すると…

$ node a.js
buffer.js:608
    slice: (buf, start, end) => buf.utf8Slice(start, end),
                                    ^

Error: Cannot create a string longer than 0x1fffffe8 characters
    at Object.slice (buffer.js:608:37)
    at Buffer.toString (buffer.js:805:14)
    at Object.readFileSync (fs.js:421:41)
    at Object.<anonymous> (/Users/fujita/xtr/BookBot/WikiEntVec/a.js:2:14)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'ERR_STRING_TOO_LONG'
}
$ 

…ズッコケます。

使ったことのある方はご存知でしょうが、 nodejs では fs.readFileSync の第2引数に文字コードを指定しておくと、 ファイルから取り込んだデータを指定した文字コードの文字列に変換してくれるのです。 その変換途中でどうやらズッコケているようです。

そこで読み込んでいるテキストファイルのサイズを確認すると…

$ ls -l entity_vector/
total 5475416
-rw-r--r--@ 1 fujita  staff   830354941  2 17  2017 entity_vector.model.bin
-rw-r--r--@ 1 fujita  staff  1951880197  2 17  2017 entity_vector.model.txt

…2GB弱の巨大なテキストファイルでした。 (Wikipedia日本語版の全文だがら当然か)

ちなみに nodejs の場合 readFileSync など インメモリに取り込むファイルのサイズの上限は2GBです。 このファイルの場合は2GBを超えてはないので、 文字コードを指定しなければ(readFileSyncを第1引数だけで呼ぶ)エラーは出ませんが、 その後文字コードの変換ができないので「万事休す」ことに変わりはありません。


nodejs でのストリーム操作

大容量ファイルへの対処方法は「ストリームによる処理」が定石と言われてます。 もちろん javascript もストリーム処理をサポートしており「nodejs ストリーム」で ググるとそのための情報をたくさん集められます。 例えば、次のページは Stream API を網羅的に紹介してくれているので便利…

qiita.com

…なんですが、javascript の Stream API は非同期で実行されるハンドラを 引き渡す仕様になっていて、そのハンドラの書き方の解説を見つけるの ひと苦労します。

そこでハンドラを書いてみました。 次のソースは2GB以上のファイルを1行ずつリードしてコンソールに表示する、 UNIXの cat コマンドみたいな javascript コードです。

const fs = require('fs');
const readline = require('readline');

var file = 'entity_vector/entity_vector.model.txt';

const reader = async function(rl) {
  for await (const chunk of rl) {
    console.log(chunk);
  }
}

async function main() {
  const rs = fs.createReadStream(file, 'utf8');
  const rl = readline.createInterface(rs, {});
  await reader(rl);
}

main();

このプログラムでは関数 reader が非同期実行できるハンドラなんですが、 関数 main では reader が全てのストリームを受け取って処理し終えるまで 待ってくれます。javascript の非同期実行といえば 昔からある promise/then を使ったコードをよく見かけますが、 このプログラムでは async/await を使ってます。

それからストリームから1行ずつデータを取り出す readline も使ってます。 関数 reader の for await of ループは UNIX の gets を使ったループと等価で console.log(chunk); で1行ごとコンソールに出力してます。 この部分を任意に書き換えて UNIX の フィルター系コマンドと 等価なコードを書くことができます。


コサイン類似度

さて、オリジナル実装に含まれている distance コマンド。 このコマンドは任意の単語を指定すると、 その単語と意味的に距離が近い単語を距離の近い順にソートして、上位40個表示してくれます。 この「意味的な距離が近い」計算とはコサイン類似度の計算で、 実は「2つの単語ベクトル間の角度」を求めています。 計算結果は1〜−1の範囲の値をとり、 1なら角度ゼロ(完全一致)、 0なら角度は90°(意味的には無関係) −1なら角度は180°(意味的には正反対?) を意味します。

Wikipedia日本語版をチェックしてみたのですが、 「コサイン類似度」というページはない( 「ベクトルのなす角」 というページ転送されます)ので、 代わりに次のページを参考にしてください。

mathtrain.jp

ちなみに、Wikipedia英語版では "Cosine similarity” というページで 「情報検索やテキストマイニングでは2つの文書がその主題に関してどれだけ類似しているかを示す有用な尺度となる」 と紹介されています。

さて、肝心のコサイン類似度の計算方法。 当初は「オリジナル実装のコードを見れば」 と考えていたのですが……… とても読解できない「古き悪しき実装」でした。 やむなく上記のページの公式を見ながら 次のコードを書きました。

function Absolute(m) {
  var ret = 0;
  for (var i = 0; i < m.length; i++) {
    ret += m[i] * m[i];
  }
  return(Math.sqrt(ret));
}

function DotProduct(m1, m2) {
  var ret = 0;
  for (var i = 0; i < m1.length; i++) {
    ret += m1[i] * m2[i];
  }
  return(ret);
}

function CosineSimilarity(dot, nrm1, nrm2) {
  return(dot/(nrm1*nrm2));
}

コサイン類似度は2つのベクトルの 内積ドット積) を各ベクトルの 絶対値 で割ることで求めます。この絶対値は各々のベクトルの長さを表し ノルム と呼ばれることもあります。 ベクトルの絶対値、内積、コサイン類似度に対応する AbsoluteDotProductCosineSimilarity を定義しました。


JavaScript 版 distance

以上のコードを組み合わせてJavaScript版 distance.js を作成しました。
ソースコードは末尾に添付しておきます。

オリジナル実装との違いは 次のようにターゲットとなる単語を引数で指定するところです。

$ ./distance.js entity_vector/entity_vector.model.txt '[アラン・チューリング]'

Word: [アラン・チューリング]
1015474 times
                                                Word       Cosine distance
--------------------------------------------------------------------------
[ジョン・フォン・ノイマン]                                      0.8238200737420581
[クロード・シャノン]                                          0.7706864375586782
[ジョン・マッカーシー]                                        0.7634118658850984
チューリング                                               0.7550014937957042
[クルト・ゲーデル]                                          0.7505430963870695
[ハーバート・サイモン]                                       0.7503034618503429
[ダフィット・ヒルベルト]                                       0.7381630672368353
[アルベルト・アインシュタイン]                                  0.7370111227611059
[スタニスワフ・ウラム]                                        0.7355724275046345
[ヴォルフガング・パウリ]                                       0.7320032112916035
[エンリコ・フェルミ]                                           0.729639429509676
[マックス・ボルン]                                          0.7238478243360493
[アロンゾ・チャーチ]                                        0.7203194651116861
[ロバート・オッペンハイマー]                                   0.7152767971441714
[ポール・ディラック]                                         0.7129907170231481
[エミー・ネーター]                                          0.7093252080731395
[ヘルマン・ワイル]                                          0.708357522309429
[エルヴィン・シュレーディンガー]                                 0.707707742898606
[ノーバート・ウィーナー]                                       0.7072144624267143
[バートランド・ラッセル]                                       0.7030623602860854
[ロジャー・ペンローズ]                                        0.7027514788704126
[ゴッドフレイ・ハロルド・ハーディ]                                0.7015682037971648
[レフ・ランダウ]                                             0.6995820039570446
アインシュタイン                                            0.6988088557431229
[アンドレ・ヴェイユ]                                         0.6978330851832386
[ヴェルナー・ハイゼンベルク]                                   0.6968193353952211
[ライナス・ポーリング]                                        0.696506621225639
数学者                                                   0.6874893585915669
[マービン・ミンスキー]                                        0.6872785430574049
[フリーマン・ダイソン]                                        0.6863152690167025
[アンリ・ポアンカレ]                                           0.6851716370245848
[ユージン・ウィグナー]                                        0.6812997448505773
[アレン・ニューウェル]                                        0.6774566693465804
[ハンス・ベーテ]                                             0.6767724362750837
[アルバート・アインシュタイン]                                    0.6750565951905274
[フリッツ・ロンドン]                                           0.6739032952575766
[リチャード・P・ファインマン]                                      0.6737536291870052
[ニールス・ボーア]                                          0.6737340516366506
[朝永振一郎]                                               0.6729520531327631
[ゴットロープ・フレーゲ]                                         0.6717680358048904
$ 

コンソール表示を見る限り オリジナル実装と遜色ない結果がでるのですが… とにかく遅い。これは教育済みデータを (ターゲットとなる 単語ベクトルを見つけるために1回、 ターゲットの単語とその他の単語との コサイン類似度を計算するためにもう1回の) 都合2回走査しているためです。


まとめ

JavaScriptスクリプト言語のなかでも高速な部類なんですが、 そのパワーを持ってしてもビッグデータを扱うことが難しいことを実感しました。 これが JavaScript機械学習系のコード実装が少ない理由なのかもしれません。

東北大学の日本語 word2vec 学習済みデータは十分にビッグデータで、 何か工夫をして扱うデータのサイズを削減しないと、 JavaScript本来のスピードで処理できないと言わざる得ません。

新しい技術的課題を見つけちゃったなぁ…

以上

PS 前回と今回のソースコードGithub にアップしました。

github.com



付録1.distance.js

#!/usr/bin/env node

var file = process.argv[2];
var keyword = process.argv[3];

const fs = require('fs');
const readline = require('readline');

function Absolute(m) {
  var ret = 0;
  for (var i = 0; i < m.length; i++) {
    ret += m[i] * m[i];
  }
  return(Math.sqrt(ret));
}

function DotProduct(m1, m2) {
  var ret = 0;
  for (var i = 0; i < m1.length; i++) {
    ret += m1[i] * m2[i];
  }
  return(ret);
}

function CosineSimilarity(dot, nrm1, nrm2) {
  return(dot/(nrm1*nrm2));
}

var n1 = 0;
var a = {};

const reader1 = async function(rs1) {
  for await (const chunk of rs1) {
    var elm = chunk.split(' ');
    if (elm[0] == keyword) {
      a = {};
      a.key = elm[0];
      a.mtx = [];
      for (var i = 1; i < elm.length; i++) {
        a.mtx.push(parseFloat(elm[i]));
      }
      a.nrm = Absolute(a.mtx);
      break;
    }
    n1++;
  }
};

var n2 = 0;
var distance = [];
const reader2 = async function(rs2) {
  for await (const chunk of rs2) {
    var elm = chunk.split(' ');
    if (elm.length > 2 && elm[0] != keyword) {
      var b = {};
      b.key = elm[0];
      b.mtx = [];
      for (var i = 1; i < elm.length; i++) {
        b.mtx.push(parseFloat(elm[i]));
      }
      b.nrm = Absolute(b.mtx);
      dot = DotProduct(a.mtx, b.mtx);
      sim = CosineSimilarity(dot, a.nrm, b.nrm);
      distance.push({ 'key': b.key, 'sim': sim });
    }
    process.stderr.write(n2+" times\r");
    n2++;
  }
};

const N = 40; // number of closest words that will be shown

function Compare(a, b) {
  return(b.sim - a.sim);
}

async function main() {
  const rs1 = fs.createReadStream(file, { encoding: 'utf8' });
  const rl1 = readline.createInterface(rs1, {});
  await reader1(rl1);
  if (!a.key) {
    console.log("Out of dictionary word!");
    return;
  }
  console.log("\nWord: %s", a.key);
  const rs2 = fs.createReadStream(file, { encoding: 'utf8' });
  const rl2 = readline.createInterface(rs2, {});
  await reader2(rl2);
  distance.sort(Compare);
  console.log("\n                                                Word       Cosine distance\n--------------------------------------------------------------------------");
  for (var i = 0; i < N; i++) {
    var pad = '                                                  ';
    var str = (distance[i].key+pad).slice(0, 50);
    console.log("%s\t%f", str, distance[i].sim);
  }
}

main();

Word2Vec(1)オリジナルの実装

Original implementation of Word2Vec


2021/01/20
藤田昭人


BookBotの対話機能には欠かせない word2vecに取り組んでます。

この不思議なアルゴリズムの解説は ブログ等で多数見つけられるのですが、 実装を紹介する記事に関しては Python ライブライブラリの gensim ほぼ一択で困惑してました。 さらに僕が探した範囲では JavaScript の word2vec のパッケージは、 いずれもオリジナルのCプログラムの ラッパーしか見当たりませんでした。

そこでオリジナルの実装を調べてみたのですが…
オリジナルの実装は 案外おじさんにも優しい古き良きプログラムでした。
本稿ではそのあたりを紹介します。


開発者のトマス・ミコロフと彼の論文について

回りくどくならないようにサクッと…

word2vec の開発者のトマス・ミコロフはWikipediaのページで紹介されています。

en.wikipedia.org

冒頭部分を抜粋すると…

Tomáš Mikolov is a Czech computer scientist working in the field of machine learning. He is currently a Research Scientist at Czech Institute of Informatics, Robotics and Cybernetics.

Mikolov has made several contributions to the field of deep learning and natural language processing. He is mostly known as the inventor of the famous Word2vec method of word embedding.

トマス・ミコロフは、機械学習の分野で活躍するチェコのコンピュータ科学者である。 現在、チェコ情報・ロボット・サイバネティクス研究所の研究員を務めています。

ミコロフは、ディープラーニング自然言語処理の分野でいくつかの貢献をしてきました。 彼は、有名な単語埋め込みのWord2Vecの発明者として知られています。

オリジナルの Word2Vec は ミコロフが Google 在籍時(?)に発表されましたが、 現在ではミコロフは Google を離れているようです*1

彼の Word2Vec の論文に関しては 次のような紹介記事があります。

hytae.hatenablog.com

この技術も 単語の埋め込みWord embedding) と呼ばれる「自然言語処理における一連の言語モデリングおよび特徴学習手法」に分類されるそうですが、 ここでいう「埋め込み」とは 数学的な埋め込み のことらしい。 気にしていると先に進めないのでひとまずは棚上げってことで進めます。


オリジナルの実装について

ミコロフが Google から離れたこともあってか、 現在オリジナルのword2vecの実装は Google Code Archive に追いやられています。またミコロフ自身も Githubソースコード を公開しています。

その他、npm にも オリジナルコードを含むパッケージが 多数存在してるので2〜3試してみたのですが、 一番クセが無いのは Googleアーカイブコードでした。 ソースをざっと見てみましたが… 意外にも古き良き (そして悪しき) Cプログラムでした。 macOS Big Sur/Xcode 12.3でコンパイルするための diffを末尾に掲載します。 (make を実行した時に妙なメッセージが出なくなるおまじない程度の修正ですが…)

この word2vecのソースコードには マニュアルが見当たりません。 (man page ぐらい書いてくれよ) が、Googleアーカイブには Word2Vec の実装に関する メモ があり、収録されている コマンドやデモ・スクリプトについて 簡単に説明されています。 オリジナル実装の貴重な情報源です。 翻訳 を用意しましたので参考にしてください。


学習のためのコマンド:word2vec

word2vecのソースコードには 都合5つのコマンドが収録されていますが、 一番重要なのは word2vec コマンドです。 オプションは次のとおり。

$ ./word2vec
ワードベクトル推定ツールキット v 0.1c

オプション: 
トレーニングのためのパラメータ: 
-train <file>
    モデルを訓練するために <file> のテキストデータを使用する
-output <file>
    結果の単語ベクトル/単語クラスタを保存するに <file> を使用する
-size <int>
    単語ベクトルのサイズを設定する。デフォルトは100。
-window <int>
    単語間の最大スキップ長を設定する。デフォルトは100。
-sample <float>
    単語の出現のしきい値を設定する。
    学習データ中に出現頻度の高い単語はランダムにダウンサンプリングされる。
    デフォルトは1e-3, 有効範囲は(0, 1e-5)
-hs <int>
    階層型ソフトマックスを使用する。デフォルトは0(使用しない)
-negative <int>
    ネガティブサンプリング値。
    デフォルトは5、一般的な値は 3 - 10です。(0 = 使用しない)
-threads <int>
    スレッドを使用する(デフォルトは12)
-iter <int>
   トレーニングのイテレーションを増やす。(デフォルトは5)
-min-count <int>
    指定された回数よりも少ない単語を破棄する。(デフォルトは5)
-alpha <float>
    学習率の初期値を設定する。
    デフォルトはスキップグラムの場合は0.025、CBOWの場合は0.05。
-classes <int>
    ワードベクトルではなく、ワードクラスを出力する。
    デフォルトのクラス数は 0 (ベクトルが書き込まれる)
-debug <int>
    デバッグモードを設定する。 
    デフォルトは 2。(トレーニング中の詳細情報を表示)
-binary <int>
    結果として得られるベクトルをバイナリモードで保存する。デフォルトは0(オフ)。
-save-vocab <file>
    ボキャブラリを <file> に保存する。
-read-vocab <file>
    ボキャブラリを <file> から読み込む。
    学習データから構築されない。
-cbow <int>
    連続 bag of words モデルを使用する。
    デフォルトは 1 (0 はskip-gram モデル)。

Examples:
./word2vec -train data.txt -output vec.txt -size 200 -window 5 -sample 1e-4 -negative 5 -hs 0 -binary 0 -cbow 1 -iter 3

$ 

word2vec コマンドは 入力のコーパスを学習して 単語毎のベクトルを生成します*2。 ここで言うコーパスとは普通の文章なのですが、このコマンドは わかち書き されていることを仮定しているので、 日本語文の場合は形態素解析機などを使って わかち書きした上でコマンドの入力とします*3


その他のコマンド

word2phrase コマンドは word2vec コマンドのプリプロセッサです。 単語ベクトルを生成する際に単語よりフレーズとして扱った方が良い場合、 例えば 'san francisco' を表現するためのベクトルは単語2つではなく、 'san_francisco' といった1つフレーズとして扱うために使用されます*4

distanceコマンドは 単語ベクトルの間の意味的距離を図るコマンドです。 下記のように "Enter word or sentence" のプロンプトで単語を入力すると その他の単語ベクトルとのコサイン距離を求め降順(意味的に近い順)に40個表示されます。

$ ./distance vectors.bin
Enter word or sentence (EXIT to break): france

Word: france  Position in vocabulary: 303

                                              Word       Cosine distance
------------------------------------------------------------------------
                                             spain      0.648269
                                             italy      0.621321
                                            french      0.614999
                                           germany      0.577397
                                          provence      0.565016
                                          ・・・
                                          philippe      0.486715
                                          brittany      0.484123
                                           austria      0.481296
                                           etienne      0.481071
                                          baudouin      0.480878
Enter word or sentence (EXIT to break): EXIT
$ 

コサイン類似度は2つのベクトルの角度を表す指標で 1から−1までの値を取ります。 ベクトルの角度がない(単語が一致する)場合は1、 ベクトルの角度が直角の場合は0になります。

word-analogyコマンドは 単語ベクトルの演算により意味的な類数をするコマンドです。 例えば vector(‘paris’) - vector('french’) + vector('berlin’) の演算は 次のような結果になります。

$ ./word-analogy vectors.bin
Enter three words (EXIT to break): paris france berlin

Word: paris  Position in vocabulary: 1055

Word: france  Position in vocabulary: 303

Word: berlin  Position in vocabulary: 1360

                                              Word              Distance
------------------------------------------------------------------------
                                           germany      0.705529
                                         reunified      0.579637
                                               gdr      0.552582
                                            russia      0.491140
                                         germanies      0.487093
                                          ・・・
                                           denmark      0.403809
                                   gleichschaltung      0.403377
                                               kpd      0.401438
                                       mecklenburg      0.399376
                                       sudetenland      0.399369
Enter three words (EXIT to break): EXIT
$ 

このコマンドは word2vec の紹介でよく語られる ベクトル演算による単語の類推をデモなのですが、 元のコーパスが十分に大きくないと期待通りの結果は出てくれないようです。 オリジナル実装に収録されているスクリプトを使うと論文で紹介されている vector(‘king’) - vector('man’) + vector('woman’) の演算結果はvector('queen’)にはなりません(vector('girl’)になります。

compute-accuracyコマンドは 単語ベクトルの品質を測るためのコマンドなんだそうですが、 この単語ベクトルの品質評価は様々な方法が提案されているそうなので、 本稿では割愛します*5

ちなみにオリジナル実装には コーパスの取得を含めたコマンドの実行環境を整えるデモ・スクリプトが付属しています。 各コマンドとスクリプトの対応は次のとおりです。

コマンド スクリプト
distance demo-word.sh, demo-phrases.sh
word-analogy demo-analogy.sh
compute-accuracy demo-word-accuracy.sh, demo-phrase-accuracy.sh
(クラスタリング) demo-classes.sh

ちなみに(クラスタリング)はword2vecコマンドを 単語のクラスタリングに使用しています。詳細はスクリプトメモ の「Word clustering(単語クラスタリング)」の項を参考にしてください。


学習済み日本語 word2vec データ

オリジナル実装に付属するデモスクリプトを使って、 英語文でのデモンストレーションばかりを見ても word2vec の効用は今ひとつピンと来ません。 ここは日本語文でも word2vec を試してみたいところですが、日本語コーパスわかち書き は悩ましいところ。そこで学習済み日本語 word2vec データを活用させてもらいます。

東北大学 乾・岡崎研究室ではWikipedia日本語版の全文を学習した日本語 word2vec データを公開しています。

www.cl.ecei.tohoku.ac.jp

20170201.tar.bz2 (2017年2月1日版, 1.3GB, 解凍後 2.6GB)をダウンロード・解凍してみたところ 以下のとおりバイナリとテキストの2種類のモデルデータが納められてました。

$ ls -l entity_vector/
total 5475416
-rw-r--r--@ 1 fujita  staff   830354941  2 17  2017 entity_vector.model.bin
-rw-r--r--@ 1 fujita  staff  1951880197  2 17  2017 entity_vector.model.txt
$ 

ミコロフの論文でも「学習精度を確保するためには億単位の単語数が必要」と書いてありましたが、 このデータはバイナリ・テキストともにGB単位の非常に大きなデータです。 オリジナル実装でもこのまま利用することができます。

$ ./distance entity_vector/entity_vector.model.bin
Enter word or sentence (EXIT to break): [アラン・チューリング]
・・・
Enter word or sentence (EXIT to break): EXIT
$ 

なお、Wikipediaの各ページのタイトルに関しては '[' と ']' で囲まれ、 空白は '_' に置き換えられていますので、ご注意ください。

大河ドラマ真田丸」を調べる

では word2vec がどんな単語が「類似性あり」と判断するか調べてみましょう。 元データが 20170201 のバックアップですので、 2016年の大河ドラマの「真田丸」に関連するキーワードについて、固有表現 (固有表現抽出を参照) に着目してリストアップしてみました。

まずドラマページ [真田丸_(NHK大河ドラマ)] ではドラマタイトル、 特に他の大河ドラマのタイトルが類似性が高いとされています。 また、大坂城の出城であった[真田丸]は、城郭と認識されたのか [熊本城]、[小谷城]、[姫路城]が類似性が高いとされています。

人物の方では、主役の[真田信繁]、兄の[真田信之]、さらに父の[真田昌幸] いずれも戦国時代の武将の名前がリストアップされていますが、 注意深く見ると[真田信繁]は豊臣方の、[真田信之]は徳川方の武将が多いように見えます。 また[真田昌幸]では織田信長家中の武将…と各々が活躍した時代が反映されているように感じますね。 次に役者の方をみると、 [真田信繁]役の[堺雅人]の場合は[佐藤隆太]と[藤原竜也]といった「基本は2枚目だがコメディもできる俳優」に見えます。 反対に[真田信之]役の[大泉洋]の場合は[ユースケ・サンタマリア]や[山口智充]といった 「基本はコメディだがシリアスもやる俳優」といったところでしょうか? [真田昌幸]役の[草刈正雄]は[京本政樹]、[多岐川裕美]、[田中麗奈]、[勝野洋] 、[阿部寛]と パッと見イメージしにくいのですが、無理矢理こじつけると「モデル上がりの俳優」ってことなのでしょうかね?

キーワードが人物である時に着目するべきことは、人物しかリストアップされないことと、 それから役名の中に俳優の名前が現れない、例えば[真田信繁]のリストに[堺雅人]は現れないことです。 この性質は脚本の[三谷幸喜]のリストを見れば一目瞭然で、 [宮藤官九郎]、[倉本聰]、[橋田壽賀子]、[北川悦吏子]、[ジェームス三木]、 [山田太一_(脚本家)]、[堤幸彦]、[菊田一夫]、[押井守]、[佐々木守]と 歴代の人気脚本家がリストアップされています。

次に真田一族の居城があった[上田市]を調べてみると、 [佐久市]、[伊那市]、[小諸市]、[千曲市]、[東御市]と 長野県下の近隣の市がリストアップされます。 居城の[上田城]で調べてみると、 [鳥取城]、[岐阜城]、[三木城]、[高遠城]、[浜松城]と 戦国史によく登場するお城がリストアップされます。 でも[大坂城]とか[江戸城]が登場しないのは何故なんでしょうか?

[真田丸_(NHK大河ドラマ)] では、 前半の見せ場だった[上田合戦]を調べると合戦のリストになり、 有名な[大坂冬の陣]、[賤ヶ岳の戦い]、[大坂夏の陣]、[山崎の戦い]を抑えて [小豆坂の戦い]、[姉川の戦い]、[月山富田城の戦い]といった地味な合戦が上位に来ています。

最後に分かりづらい年号ですが、 本能寺の変が起きた[1582年]に一番近いのは武田氏が滅亡した[1567年]です。 関ヶ原の戦いが起きた[1600年]に一番近いのは小田原征伐が起きた[1590年]です。 さらに大坂冬の陣が起きた[1614年]と大坂夏の陣が起きた[1615年]は 相互にもっとも類似していると出ています。(ちょっとこじつけ過ぎか?)

もちろん word2vec コマンドは各キーワードが出現するパターンを調べているだけで、 各キーワードが持つ意味を理解している訳ではないですが、 このような「ついついキーワード間の関係の意味づけ」を追いかけてしまうような特性、 これがミコロフの言う「単語ベクトルの面白い性質」ということなのでしょう。

以上


付録1.macOS Big Sur/Xcode 12.3でコンパイルするためのdiff
diff -ru word2vec/trunk/compute-accuracy.c word2vec-mac/compute-accuracy.c
--- word2vec/trunk/compute-accuracy.c 2016-03-18 13:03:48.000000000 +0900
+++ word2vec-mac/compute-accuracy.c   2021-01-11 10:43:11.000000000 +0900
@@ -16,7 +16,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
-#include <malloc.h>
+#include <stdlib.h>
 #include <ctype.h>
 
 const long long max_size = 2000;         // max length of strings
@@ -26,7 +26,7 @@
 int main(int argc, char **argv)
 {
   FILE *f;
-  char st1[max_size], st2[max_size], st3[max_size], st4[max_size], bestw[N][max_size], file_name[max_size], ch;
+  char st1[max_size], st2[max_size], st3[max_size], st4[max_size], bestw[N][max_size], file_name[max_size]; //, ch;
   float dist, len, bestd[N], vec[max_size];
   long long words, size, a, b, c, d, b1, b2, b3, threshold = 0;
   float *M;
diff -ru word2vec/trunk/distance.c word2vec-mac/distance.c
--- word2vec/trunk/distance.c 2016-03-18 13:03:48.000000000 +0900
+++ word2vec-mac/distance.c   2021-01-11 10:41:43.000000000 +0900
@@ -15,7 +15,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <math.h>
-#include <malloc.h>
+#include <stdlib.h>
 
 const long long max_size = 2000;         // max length of strings
 const long long N = 40;                  // number of closest words that will be shown
@@ -28,7 +28,7 @@
   char file_name[max_size], st[100][max_size];
   float dist, len, bestd[N], vec[max_size];
   long long words, size, a, b, c, d, cn, bi[100];
-  char ch;
+  //char ch;
   float *M;
   char *vocab;
   if (argc < 2) {
diff -ru word2vec/trunk/word-analogy.c word2vec-mac/word-analogy.c
--- word2vec/trunk/word-analogy.c 2016-03-18 13:03:48.000000000 +0900
+++ word2vec-mac/word-analogy.c   2021-01-11 10:42:03.000000000 +0900
@@ -15,7 +15,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <math.h>
-#include <malloc.h>
+#include <stdlib.h>
 
 const long long max_size = 2000;         // max length of strings
 const long long N = 40;                  // number of closest words that will be shown
@@ -28,7 +28,7 @@
   char file_name[max_size], st[100][max_size];
   float dist, len, bestd[N], vec[max_size];
   long long words, size, a, b, c, d, cn, bi[100];
-  char ch;
+  //char ch;
   float *M;
   char *vocab;
   if (argc < 2) {


付録2.大河ドラマ「真田丸」関連キーワードのdistance
$ ./distance entity_vector/entity_vector.model.bin
Enter word or sentence (EXIT to break): [真田丸_(NHK大河ドラマ)]

Word: [真田丸_(NHK大河ドラマ)]  Position in vocabulary: 208717

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [八重の桜]      0.850641
                                 [軍師官兵衛]      0.827400
                  [平清盛_(NHK大河ドラマ)]      0.825276
                                       [花燃ゆ]      0.819338
               [風林火山_(NHK大河ドラマ)]      0.791861
                  [天地人_(NHK大河ドラマ)]      0.780095
                                       [龍馬伝]      0.768709
                                    [マッサン]      0.767875
               [功名が辻_(NHK大河ドラマ)]      0.739119
                                 [あさが来た]      0.729785
         [フルスイング_(テレビドラマ)]      0.722142
                                 [花子とアン]      0.720576
                                    [鈴木先生]      0.717524
               [おひさま_(テレビドラマ)]      0.714102
      [カーネーション_(テレビドラマ)]      0.708059
                     [篤姫_(NHK大河ドラマ)]      0.705406
            [動物戦隊ジュウオウジャー]      0.700339
                     [Mother_(テレビドラマ)]      0.697521
                                      [新選組!]      0.696299
        [つばさ_(2009年のテレビドラマ)]     0.694637
                                    [てっぱん]      0.693992
                                 [最高の離婚]      0.686887
                                 [TOUCH/タッチ]      0.683295
[ドラえもん_新・のび太と鉄人兵団_�Jiří     0.682352
                                 [ナカイの窓]      0.681161
                        [NHK連続テレビ小説]      0.679608
        [水野真紀の魔法のレストランR]      0.679500
                              [NHK大河ドラマ]      0.678539
               [逃げるは恥だが役に立つ]      0.677009
     [ドラえもん_のび太の宇宙英雄記]      0.676890
                        [にほんごであそぼ]      0.676332
                              [お台場新大陸]      0.675701
                              [梅ちゃん先生]      0.675567
[花ざかりの君たちへ_(テレビドラマ)][ピルトダウン人]     0.675254
                     [爆報!_THE_フライデー]        0.674606
  [ドラえもん_新・のび太の日本誕生]      0.674550
                      [67回NHK紅白歌合戦]     0.674473
                                   テレビ大阪      0.674378
            [坂の上の雲_(テレビドラマ)]      0.673822
Enter word or sentence (EXIT to break): [真田丸]

Word: [真田丸]  Position in vocabulary: 130833

                                              Word       Cosine distance
------------------------------------------------------------------------
                                            出城      0.740320
                                       [熊本城]      0.728834
                                       [小谷城]      0.724149
                                       [姫路城]      0.714244
                                         二ノ丸      0.714124
                                       [松本城]      0.708700
                                       [彦根城]      0.708108
                           [Category:豊臣秀吉]      0.707795
                                    [岸和田城]      0.706999
                           [福山城_(備後国)]      0.705762
                                       [二の丸]      0.701598
                                            出丸      0.700790
                                    [勝竜寺城]      0.700491
                                       [金沢城]      0.700444
                                       [玉縄城]      0.700341
                                       [滝山城]      0.697077
                                            本丸      0.696883
                                         三の丸      0.694887
                                         平山城      0.691086
                                    [名古屋城]      0.688279
                                       [川越城]      0.685610
                                          [淀城]      0.685132
                                         古戦場      0.685010
                                            山砦      0.683218
                                       [丸根砦]      0.682380
                                       [福岡城]      0.682332
                                       [鷲津砦]      0.681645
                                       [府内城]      0.681402
                                       [茨木城]      0.680548
                                       [仙台城]      0.677954
                           [高松城_(讃岐国)]      0.675755
                                         二の丸      0.675349
                                       [安土城]      0.675332
                                          [忍城]      0.675065
                                         天守閣      0.674914
                                       [岐阜城]      0.673553
                                          [本丸]      0.673221
                                            清洲      0.673103
                                       [掛川城]      0.671609
                                          [水城]      0.671606
Enter word or sentence (EXIT to break): [真田信繁]

Word: [真田信繁]  Position in vocabulary: 53057

                                              Word       Cosine distance
------------------------------------------------------------------------
                                            幸村      0.862280
                                            信繁      0.853500
                                    [黒田孝高]      0.851998
                                    [前田利家]      0.845007
                                    [後藤基次]      0.841422
                                    [大谷吉継]      0.833924
                                 [宇喜多秀家]      0.831085
                                    [真田昌幸]      0.830969
                                    [本多忠勝]      0.830236
                                    [浅井長政]      0.829288
                                    [竹中重治]      0.827986
                                    [丹羽長秀]      0.827290
                                       [堀秀政]      0.824391
                                    [石田三成]      0.824265
                                    [井伊直政]      0.823248
                                    [明智光秀]      0.821230
                                 [佐久間信盛]      0.820919
                                    [馬場信春]      0.820386
                                    [池田恒興]      0.819985
                                    [鳥居元忠]      0.817749
                                    [藤堂高虎]      0.816229
                                    [木村重成]      0.813626
                                    [直江兼続]      0.812626
                                 [小早川秀秋]      0.812217
                                    [長束正家]      0.811567
                                    [浅野長政]      0.810507
                                    [酒井忠次]      0.809801
                                    [細川幽斎]      0.809242
                                    [山県昌景]      0.807876
                              [長宗我部盛親]      0.806687
                                 [佐久間盛政]      0.806065
                                    [立花宗茂]      0.802603
                                    [織田信忠]      0.802597
                                    [黒田長政]      0.802305
                                       [森可成]      0.800703
                                    [織田信孝]      0.799144
                                    [柴田勝家]      0.798669
                                    [立花道雪]      0.798262
                                    [毛利勝永]      0.796287
                                    [水野勝成]      0.795799
Enter word or sentence (EXIT to break): [堺雅人]

Word: [堺雅人]  Position in vocabulary: 168294

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [佐藤隆太]      0.829818
                                    [藤原竜也]      0.823819
                              [佐藤健_(俳優)]      0.817278
                                       [向井理]      0.810610
                                    [萩原聖人]      0.808793
                                       [石黒賢]      0.804831
                                    [山田孝之]      0.803835
                                    [田中麗奈]      0.801961
                                    [市原隼人]      0.799150
                                    [栗山千明]      0.798809
                                 [松嶋菜々子]      0.794591
                                    [中谷美紀]      0.794233
                                 [石原さとみ]      0.790625
                                    [仲里依紗]      0.789875
                                    [本木雅弘]      0.789623
                                    [堀北真希]      0.789125
                                    [岡田将生]      0.786672
                                    [陣内孝則]      0.786410
                                    [唐沢寿明]      0.786037
                                       [山崎努]      0.785783
                                 [石田ゆり子]      0.785428
                                    [岡田准一]      0.784751
                                    [織田裕二]      0.784626
                                    [高橋克典]      0.784127
                                    [杏_(女優)]      0.783930
                                    [前田亜季]      0.783606
                                 [かたせ梨乃]      0.783165
                                    [谷村美月]      0.783153
                                       [橋爪功]      0.782846
                                    [小林稔侍]      0.782647
                                    [北川景子]      0.782442
                                    [溝端淳平]      0.781518
                                 [稲森いずみ]      0.781370
                                 [加藤ローサ]      0.780886
                                    [塚本高史]      0.780642
                                    [上野樹里]      0.780136
                          [中村獅童_(2代目)]     0.779944
                                    [真矢みき]      0.779365
                                    [生瀬勝久]      0.778443
                                    [成海璃子]      0.778381
Enter word or sentence (EXIT to break): [真田信之]

Word: [真田信之]  Position in vocabulary: 71834

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [結城秀康]      0.864722
                                    [細川忠興]      0.854418
                                    [松平忠直]      0.853225
                                    [前田利長]      0.849034
                                    [真田昌幸]      0.846276
                                    [松平信康]      0.843927
                                    [豊臣秀長]      0.839133
                                    [松平忠輝]      0.838586
                                    [浅野長政]      0.838264
                                    [徳川忠長]      0.836924
                                 [小笠原秀政]      0.836570
                                    [本多忠政]      0.836416
                                            忠政      0.835936
                                       [堀秀政]      0.834228
                                    [立花宗茂]      0.831016
                                    [北条氏照]      0.830443
                                    [松平広忠]      0.829907
                                    [黒田長政]      0.829375
                                    [吉川広家]      0.827719
                                    [池田輝政]      0.827295
                                    [前田利家]      0.826828
                                    [京極高次]      0.826813
                                            隆景      0.826811
                                    [羽柴秀勝]      0.823404
                                            輝元      0.822894
                                            信政      0.822387
                                    [織田信孝]      0.821835
                                    [織田信雄]      0.820363
                                            秀康      0.819313
                                    [本多正信]      0.818753
                                            信繁      0.818476
                                    [伊達輝宗]      0.818328
                                            藤孝      0.818125
                                    [毛利隆元]      0.817783
                                    [平岩親吉]      0.817510
                                    [浅野幸長]      0.817215
                                       [堀秀治]      0.817149
                                    [松平忠昌]      0.815690
                                    [黒田職隆]      0.814530
                                    [蒲生氏郷]      0.814355
Enter word or sentence (EXIT to break): [大泉洋]

Word: [大泉洋]  Position in vocabulary: 95697

                                              Word       Cosine distance
------------------------------------------------------------------------
               [ユースケ・サンタマリア]      0.847172
                                    [山口智充]      0.842746
                                 [佐藤江梨子]      0.839287
                                    [西田敏行]      0.828026
                                       [小栗旬]      0.822958
                                       [藤井隆]      0.822931
                                 [長澤まさみ]      0.822114
                                    [伊東四朗]      0.821836
                              [ラサール石井]      0.821569
                                    [内村光良]      0.817735
                                    [高田純次]      0.817491
                                    [広末涼子]      0.816757
                                    [久本雅美]      0.814944
                                       [安田顕]      0.812524
                                    [陣内智則]      0.811957
                                    [中村玉緒]      0.811861
                                    [香取慎吾]      0.811576
                                    [木村拓哉]      0.810266
                                    [板尾創路]      0.809218
                                    [石坂浩二]      0.809071
                                 [バカリズム]      0.806382
                                    [小池栄子]      0.805978
                                    [山田邦子]      0.805532
                                    [武田鉄矢]      0.805505
                                    [江口洋介]      0.805399
                                    [小倉優子]      0.805051
                                    [坂上二郎]      0.804709
                                 [松嶋菜々子]      0.804488
                                       [坂上忍]      0.803884
                                 [片岡鶴太郎]      0.802868
                                       [堺正章]      0.802248
                                    [織田裕二]      0.802003
                                          [優香]      0.801509
                                    [松村邦洋]      0.801336
                                       [関根勤]      0.801172
                                 [劇団ひとり]      0.800607
                                    [陣内孝則]      0.800257
                              [せんだみつお]      0.799715
                                    [舘ひろし]      0.799632
                     [宮川大輔_(タレント)]      0.799621
Enter word or sentence (EXIT to break): [真田昌幸]

Word: [真田昌幸]  Position in vocabulary: 52729

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [浅井長政]      0.889628
                                       [森長可]      0.888692
                                    [柴田勝家]      0.887071
                                    [吉川元春]      0.885952
                                    [北条氏邦]      0.885003
                                    [丹羽長秀]      0.880816
                                    [滝川一益]      0.880605
                  [佐竹義重_(十八代当主)]      0.878321
                                       [堀秀政]      0.877057
                                    [北条氏照]      0.875241
                                 [佐久間信盛]      0.874229
                                 [宇喜多秀家]      0.871974
                                    [立花宗茂]      0.869287
                                    [佐々成政]      0.868931
                                    [織田信孝]      0.868109
                                    [池田恒興]      0.867718
                                    [吉川広家]      0.866312
                                       [森可成]      0.865885
                                    [浅野長政]      0.865563
                                    [朝倉義景]      0.864799
                              [長宗我部盛親]      0.864381
                                    [織田信雄]      0.863931
                                    [今川義元]      0.863850
                                    [立花道雪]      0.863241
                                    [長束正家]      0.862811
                                    [前田利家]      0.862411
                                    [北条氏康]      0.861690
                                    [十河存保]      0.861266
                                    [伊達輝宗]      0.860470
                                    [北条高広]      0.860067
                                 [小早川秀秋]      0.859652
                                    [北条氏直]      0.859366
                                    [里見義堯]      0.858346
                                    [本庄繁長]      0.856125
                                 [小早川隆景]      0.855965
                                    [結城晴朝]      0.855387
                                    [筒井順慶]      0.854689
                                    [山県昌景]      0.854278
                                    [上杉景勝]      0.853994
                                    [北条氏綱]      0.853868
Enter word or sentence (EXIT to break): [草刈正雄]

Word: [草刈正雄]  Position in vocabulary: 194460

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [京本政樹]      0.809349
                                 [多岐川裕美]      0.806034
                                    [田中麗奈]      0.804275
                                       [勝野洋]      0.795097
                                       [阿部寛]      0.792035
                                    [田中邦衛]      0.791783
                                    [渡辺裕之]      0.789512
                                    [高橋克典]      0.786939
                                 [由美かおる]      0.786617
                                    [松坂慶子]      0.786271
                                 [かたせ梨乃]      0.785857
                                 [和久井映見]      0.785291
                                    [渡瀬恒彦]      0.784532
                                       [加藤剛]      0.783957
                                    [川谷拓三]      0.783735
                                    [宇津井健]      0.783284
                                    [大原麗子]      0.782354
                                       [宍戸錠]      0.782185
                                    [唐沢寿明]      0.781887
                                    [佐藤隆太]      0.781781
                                    [北村一輝]      0.781706
                                       [竜雷太]      0.781700
                                       [水谷豊]      0.780734
                                    [内藤剛志]      0.780645
                                          [瑛太]      0.780475
                                    [佐藤浩市]      0.780325
                              [佐藤健_(俳優)]      0.777771
                                    [池内淳子]      0.777584
                                    [菅原文太]      0.777021
                                    [小林稔侍]      0.776970
                                    [沢口靖子]      0.776846
                                    [柳葉敏郎]      0.776495
                                    [津川雅彦]      0.775650
                                    [高嶋政伸]      0.775082
                                    [石立鉄男]      0.774510
                                       [石黒賢]      0.773385
                                    [香川照之]      0.773220
                                       [山崎努]      0.773158
                                 [中条きよし]      0.772763
                                 [三田村邦彦]      0.772160
Enter word or sentence (EXIT to break): [三谷幸喜]

Word: [三谷幸喜]  Position in vocabulary: 62278

                                              Word       Cosine distance
------------------------------------------------------------------------
                                 [宮藤官九郎]      0.847801
                                       [倉本聰]      0.828014
                                 [橋田壽賀子]      0.813948
                                 [北川悦吏子]      0.804359
                           [ジェームス三木]      0.782101
                        [山田太一_(脚本家)]      0.779650
                                       [堤幸彦]      0.774029
                                    [菊田一夫]      0.772732
                                       [押井守]      0.769006
                                    [佐々木守]      0.765393
                                    [井上敏樹]      0.756454
                                       [早坂暁]      0.755188
                                    [岩井俊二]      0.748714
                                       [宮崎駿]      0.744259
                                 [井上ひさし]      0.743560
                                    [鴻上尚史]      0.742345
                                    [上原正三]      0.739719
                                    [向田邦子]      0.738351
                                    [蜷川幸雄]      0.736478
                                    [君塚良一]      0.731993
                                    [伊藤和典]      0.731116
                                    [野島伸司]      0.730984
                                    [大和屋竺]      0.729276
                                    [井筒和幸]      0.727722
                                       [細田守]      0.727141
                                 [石井ふく子]      0.726934
                                    [伊丹十三]      0.726323
                                    [市川森一]      0.725571
                                    [清水邦夫]      0.725172
                                       [橋本忍]      0.721844
                                    [金子修介]      0.720818
                                    [大林宣彦]      0.720543
                                       [花登筺]      0.719697
                                   アニメ監督      0.716220
                                       [北野武]      0.716118
                     [リリー・フランキー]      0.715990
                                    [竹中直人]      0.714659
                                    [樋口真嗣]      0.714394
                                    [久世光彦]      0.712585
                                       [伊上勝]      0.712067
Enter word or sentence (EXIT to break): [上田市]

Word: [上田市]  Position in vocabulary: 21341

                                              Word       Cosine distance
------------------------------------------------------------------------
                                       [佐久市]      0.873024
                                       [伊那市]      0.851494
                                       [小諸市]      0.851373
                                       [千曲市]      0.850226
                                       [東御市]      0.842370
                                       [青木村]      0.840791
                                       [飯田市]      0.837979
                                       [塩尻市]      0.828366
                                       [丸子町]      0.827832
                                       [辰野町]      0.825878
                                    [上伊那郡]      0.824890
                                       [小県郡]      0.823911
                                       [諏訪市]      0.820981
                                       [中野市]      0.818131
                                       [飯山市]      0.816464
                                       [長野市]      0.814474
                                       [岡谷市]      0.814004
                                    [上水内郡]      0.813308
                                       [松本市]      0.813192
                                    [東筑摩郡]      0.811427
                                    [下伊那郡]      0.808146
                                       [大町市]      0.803682
                                       [坂城町]      0.799765
                                       [木曽町]      0.799210
                                       [茅野市]      0.798991
                                    [駒ヶ根市]      0.796395
                                       [須坂市]      0.792879
                                    [安曇野市]      0.789478
                                       [信濃町]      0.789210
                                    [南安曇郡]      0.789018
                                       [高遠町]      0.788462
                                       [長和町]      0.785993
                           [高山村_(長野県)]      0.783720
                                       [木曽郡]      0.783370
                                    [上高井郡]      0.781338
                                       [飯綱町]      0.780839
                                       [真田町]      0.777769
                                    [北佐久郡]      0.771381
                                       [小谷村]      0.771205
                                    [小布施町]      0.770121
Enter word or sentence (EXIT to break): [上田城]

Word: [上田城]  Position in vocabulary: 79257

                                              Word       Cosine distance
------------------------------------------------------------------------
                                       [鳥取城]      0.852216
                                       [岐阜城]      0.849356
                                       [三木城]      0.849105
                                       [高遠城]      0.845153
                                       [浜松城]      0.843289
                                          [忍城]      0.839791
                           [高松城_(備中国)]      0.839502
                                       [岩村城]      0.837147
                                    [真田昌幸]      0.836247
                                    [佐和山城]      0.836211
                                       [大垣城]      0.835653
                                       [二俣城]      0.835134
                                       [伊丹城]      0.831016
                                       [小松城]      0.827549
                                       [大津城]      0.826306
                                       [高槻城]      0.826240
                                       [長篠城]      0.822233
                                    [二本松城]      0.821886
                                       [掛川城]      0.819836
                                       [上月城]      0.819801
                                    [島津家久]      0.817774
                                       [沼田城]      0.814995
                                    [小田原城]      0.813866
                                 [月山富田城]      0.811999
                                    [立花山城]      0.811840
                                    [勝竜寺城]      0.811324
                                       [清洲城]      0.810574
                                    [松井田城]      0.809341
                                       [七尾城]      0.809190
                                    [佐々成政]      0.808459
                                    [滝川一益]      0.808450
                                 [佐久間信盛]      0.805699
                                       [有岡城]      0.804309
                                       [小谷城]      0.803942
                                       [森長可]      0.803854
                                       [飫肥城]      0.803733
                                       [箕輪城]      0.803318
                                       [桑名城]      0.802203
                           [田辺城_(丹後国)]      0.799411
                                    [高天神城]      0.798767
Enter word or sentence (EXIT to break): [上田合戦]

Word: [上田合戦]  Position in vocabulary: 142642

                                              Word       Cosine distance
------------------------------------------------------------------------
                              [小豆坂の戦い]      0.830107
                                 [姉川の戦い]      0.820177
                        [月山富田城の戦い]      0.810359
                                 [大坂冬の陣]      0.808880
                              [賤ヶ岳の戦い]      0.802219
                                 [大坂夏の陣]      0.801875
                                 [山崎の戦い]      0.799450
                                    [大坂の役]      0.796623
                                    [会津戦争]      0.796386
                           [三方ヶ原の戦い]      0.796324
                              [戸次川の戦い]      0.795473
                              [伏見城の戦い]      0.794881
                     [小牧・長久手の戦い]      0.793958
                     [天王寺・岡山の戦い]      0.793225
                                    [大坂の陣]      0.792602
                              [石垣原の戦い]      0.791799
                                   大坂夏の陣      0.790713
                                 [蟹江城合戦]      0.782472
                                    [奥州合戦]      0.782259
                              [教興寺の戦い]      0.780309
                  [野田城・福島城の戦い]      0.779296
                                    [四国攻め]      0.778365
                              [慶長出羽合戦]      0.778106
                                 [厳島の戦い]      0.777675
                              [上月城の戦い]      0.776749
                                    [北越戦争]      0.775847
                                 [長篠の戦い]      0.773696
                        [加治田・兼山合戦]      0.773451
                           [甲州勝沼の戦い]      0.772457
                              [桶狭間の戦い]      0.765290
                              [白河口の戦い]      0.765117
                                 [国府台合戦]      0.764990
                              [道明寺の戦い]      0.764268
                                 [船岡山合戦]      0.762874
                              [田辺城の戦い]      0.762374
                                    [紀州征伐]      0.761690
                                 [耳川の戦い]      0.760473
                                    [会津征伐]      0.760299
                              [三増峠の戦い]      0.760033
                                 [天狗党の乱]      0.759361
Enter word or sentence (EXIT to break): [1582]

Word: [1582]  Position in vocabulary: 13930

                                              Word       Cosine distance
------------------------------------------------------------------------
                                         [1567]     0.871124
                                         [1584]     0.869043
                                         [1581]     0.856171
                                         [1587]     0.855367
                                         [1575]     0.851390
                                         [1578]     0.846633
                                         [1585]     0.844765
                                         [1572]     0.844028
                                         [1541]     0.842195
                                           15820.841818
                                         [1577]     0.840910
                                         [1579]     0.839901
                                         [1560]     0.839451
                                         [1583]     0.838247
                                         [1576]     0.837940
                                         [1551]     0.835917
                                         [1586]     0.834452
                                         [1569]     0.834166
                                         [1574]     0.832817
                                         [1568]     0.831446
                                         [1589]     0.830784
                                         [1562]     0.824858
                                         [1571]     0.823303
                                         [1548]     0.820335
                                         [1561]     0.817914
                                         [1573]     0.816230
                                         [1570]     0.816178
                                         [1564]     0.815748
                                         [1546]     0.814555
                                         [1554]     0.813623
                                         [1593]     0.813225
                                         [1563]     0.812475
                                         [1565]     0.811689
                                         [1591]     0.808009
                                         [1588]     0.804201
                                         [1555]     0.803827
                                         [1566]     0.803808
                                         [1598]     0.802000
                                         [1552]     0.799705
                                         [1549]     0.798940
Enter word or sentence (EXIT to break): [1600]

Word: [1600]  Position in vocabulary: 11381

                                              Word       Cosine distance
------------------------------------------------------------------------
                                         [1590]     0.840695
                                         [1615]     0.822067
                                         [1583]     0.819413
                                         [1584]     0.818799
                                         [1595]     0.813488
                                         [1587]     0.808977
                                         [1602]     0.804080
                                         [1592]     0.801207
                                         [1585]     0.800537
                                         [1599]     0.800520
                                         [1598]     0.800442
                                         [1601]     0.795140
                                         [1588]     0.793869
                                         [1570]     0.792645
                                         [1582]     0.792580
                                         [1586]     0.789530
                                         [1560]     0.787423
                                         [1581]     0.787366
                                         [1596]     0.787313
                                         [1614]     0.787291
                                         [1593]     0.786871
                                         [1562]     0.785282
                                         [1589]     0.784301
                                         [1597]     0.780177
                                         [1575]     0.779594
                                         [1591]     0.779534
                                         [1580]     0.778075
                                         [1577]     0.777516
                                         [1561]     0.774325
                                         [1578]     0.773667
                                         [1594]     0.770959
                                         [1605]     0.770836
                                         [1572]     0.768102
                                         [1608]     0.766482
                                         [1604]     0.766143
                                         [1568]     0.763673
                                         [1540]     0.761703
                                         [1603]     0.759373
                                         [1632]     0.759193
                                         [1573]     0.757969
Enter word or sentence (EXIT to break): [関ケ原町]

Word: [関ケ原町]  Position in vocabulary: 76997

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [南越前町]      0.708150
                                       [大野市]      0.705391
                                       [足柄峠]      0.698684
                                            墨俣      0.695876
                                       [不破郡]      0.693560
                                       [伊那谷]      0.685688
                           [和田峠_(長野県)]      0.678445
                                 [倶利伽羅峠]      0.676838
                                       [垂井町]      0.675981
                                       [小仏峠]      0.670487
                                       [敦賀市]      0.667752
                                    [上野原市]      0.663754
                                          [中濃]      0.662866
                                    [生駒山地]      0.662561
                                       [只見町]      0.661956
                                    [揖斐川町]      0.661821
                                    [長野盆地]      0.660612
                                       [美濃市]      0.660384
                                 [会津美里町]      0.659184
                                    [国道156]     0.657741
                                         中津川      0.656688
                                          [屋島]      0.656632
                                       [塩尻市]      0.656036
                                       [恵那市]      0.655065
                                       [籠坂峠]      0.654692
                                       [天川村]      0.654580
                              [上村_(長野県)]      0.653949
                                    [木ノ芽峠]      0.653118
                                       [親不知]      0.651887
                                       [笠松町]      0.650014
                                    [木曽地域]      0.649544
                                       [本宮市]      0.647530
                                    [諏訪盆地]      0.647047
                                       [禅定道]      0.646896
                                       [笹子峠]      0.646397
                           [大野郡_(福井県)]      0.645612
                                    [丹波山村]      0.644715
                                       [坂井市]      0.643866
                                       [羽咋市]      0.643366
                                       [加茂郡]      0.643256
Enter word or sentence (EXIT to break): [関ヶ原の戦い]

Word: [関ヶ原の戦い]  Position in vocabulary: 8836

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [大坂の陣]      0.881696
                     [小牧・長久手の戦い]      0.851104
                                    [大坂の役]      0.850898
                                 [小田原征伐]      0.841286
                                 [大坂夏の陣]      0.830540
                              [賤ヶ岳の戦い]      0.810479
                              [桶狭間の戦い]      0.809550
                                 [大坂冬の陣]      0.805582
                                         関ヶ原      0.798661
                                 [山崎の戦い]      0.782722
                                    [九州征伐]      0.772222
                                関ヶ原の戦い      0.767118
                                    [承久の乱]      0.761580
                                    [奥州合戦]      0.759930
                              [戸次川の戦い]      0.759800
                           [文禄・慶長の役]      0.759067
                                 [長篠の戦い]      0.758771
                           [三方ヶ原の戦い]      0.756944
                                 [厳島の戦い]      0.756609
                                    [九州平定]      0.754406
                                 [本能寺の変]      0.751754
                                   大坂夏の陣      0.746492
                              [伏見城の戦い]      0.745927
                              [九戸政実の乱]      0.745615
                                    [戊辰戦争]      0.744409
                                    [上田合戦]      0.734638
                              [慶長出羽合戦]      0.733284
                                 [姉川の戦い]      0.732787
                                    [文禄の役]      0.726051
                                            西軍      0.725281
                              [一ノ谷の戦い]      0.725120
                                       [徳川氏]      0.724901
                                    [島原の乱]      0.724819
                                    [上杉景勝]      0.723127
                              [沖田畷の戦い]      0.722916
                        [加治田・兼山合戦]      0.719709
                                    [石山合戦]      0.717034
                                 [観応の擾乱]      0.715840
                                    [四国攻め]      0.715689
                        [鳥羽・伏見の戦い]      0.715559
Enter word or sentence (EXIT to break): [1614]

Word: [1614]  Position in vocabulary: 20257

                                              Word       Cosine distance
------------------------------------------------------------------------
                                         [1615]     0.865162
                                         [1591]     0.860337
                                         [1611]     0.859098
                                         [1613]     0.858774
                                         [1623]     0.858637
                                         [1612]     0.858610
                                         [1610]     0.849760
                                         [1602]     0.848271
                                         [1586]     0.848025
                                         [1588]     0.846918
                                         [1589]     0.844200
                                         [1608]     0.843631
                                         [1609]     0.842249
                                         [1604]     0.840114
                                         [1603]     0.839589
                                         [1597]     0.838903
                                         [1637]     0.838608
                                         [1621]     0.837954
                                         [1672]     0.837648
                                         [1587]     0.835050
                                         [1598]     0.833399
                                         [1634]     0.832883
                                         [1590]     0.832396
                                         [1605]     0.830919
                                         [1601]     0.830424
                                         [1584]     0.829095
                                         [1592]     0.828330
                                         [1622]     0.828188
                                         [1632]     0.828161
                                         [1635]     0.827146
                                         [1585]     0.827070
                                         [1617]     0.826413
                                         [1619]     0.826360
                                         [1639]     0.825553
                                         [1638]     0.824017
                                         [1583]     0.823912
                                         [1606]     0.822882
                                         [1599]     0.820057
                                         [1641]     0.819394
                                         [1643]     0.819274
Enter word or sentence (EXIT to break): [1615]

Word: [1615]  Position in vocabulary: 18770

                                              Word       Cosine distance
------------------------------------------------------------------------
                                         [1614]     0.865162
                                         [1596]     0.864532
                                         [1602]     0.863315
                                         [1592]     0.860134
                                         [1591]     0.855674
                                         [1616]     0.852244
                                         [1597]     0.850820
                                         [1612]     0.848244
                                         [1598]     0.847103
                                         [1601]     0.844938
                                         [1611]     0.842920
                                         [1587]     0.837137
                                         [1593]     0.836945
                                         [1603]     0.836894
                                         [1610]     0.836832
                                         [1588]     0.836775
                                         [1613]     0.836743
                                         [1594]     0.836655
                                         [1589]     0.836058
                                         [1583]     0.835774
                                         [1605]     0.835619
                                         [1599]     0.835035
                                         [1623]     0.833858
                                         [1643]     0.832430
                                         [1638]     0.829741
                                         [1617]     0.827716
                                         [1585]     0.827511
                                         [1586]     0.827284
                                         [1604]     0.825798
                                         [1590]     0.825550
                                         [1595]     0.825356
                                         [1633]     0.824429
                                         [1573]     0.824042
                                         [1624]     0.823928
                                         [1606]     0.822988
                                         [1609]     0.822239
                                         [1600]     0.822067
                                         [1584]     0.820829
                                         [1608]     0.820579
                                         [1619]     0.819566
Enter word or sentence (EXIT to break): [大坂城]

Word: [大坂城]  Position in vocabulary: 17705

                                              Word       Cosine distance
------------------------------------------------------------------------
                                       [伏見城]      0.866872
                                       [江戸城]      0.866639
                                       [駿府城]      0.853587
                                         大坂城      0.849981
                                       [二条城]      0.816748
                                    [小田原城]      0.811317
                                       [岐阜城]      0.807676
                                    [佐和山城]      0.784947
                                       [大垣城]      0.772539
                                       [鳥取城]      0.766673
                                         江戸城      0.763479
                                       [浜松城]      0.763274
                                       [伊丹城]      0.758546
                                    [宇都宮城]      0.758494
                                         西の丸      0.757409
                                       [富山城]      0.757330
                                    [多聞山城]      0.756187
                                       [若松城]      0.750989
                                       [川越城]      0.749303
                                       [三木城]      0.746179
                                       [広島城]      0.743387
                                       [安土城]      0.741307
                                            本丸      0.741182
                                            陣所      0.739956
                                       [岡山城]      0.739198
                                       [高槻城]      0.738477
                                       [水戸城]      0.737631
                                       [山形城]      0.736139
                                    [二本松城]      0.734716
                                       [尼崎城]      0.732571
                                       [古河城]      0.731697
                                 [石山本願寺]      0.731335
                                       [清洲城]      0.731308
                                       [長岡城]      0.731267
                                    [春日山城]      0.731238
                                       [岩槻城]      0.730846
                                         二の丸      0.730584
                                       [岡崎城]      0.730406
                                    [岸和田城]      0.727810
                                       [小諸城]      0.724366
Enter word or sentence (EXIT to break): [大坂の陣]

Word: [大坂の陣]  Position in vocabulary: 20364

                                              Word       Cosine distance
------------------------------------------------------------------------
                                    [大坂の役]      0.929648
                                 [大坂夏の陣]      0.905435
                     [小牧・長久手の戦い]      0.887993
                              [関ヶ原の戦い]      0.881696
                                 [大坂冬の陣]      0.880892
                                 [小田原征伐]      0.842685
                              [賤ヶ岳の戦い]      0.827607
                           [文禄・慶長の役]      0.826194
                                   大坂夏の陣      0.809155
                                 [長篠の戦い]      0.808281
                                    [九州平定]      0.805219
                                    [文禄の役]      0.802838
                                 [姉川の戦い]      0.800240
                           [三方ヶ原の戦い]      0.793948
                                         関ヶ原      0.793667
                                    [上田合戦]      0.792602
                                    [九州征伐]      0.790378
                              [戸次川の戦い]      0.787284
                              [桶狭間の戦い]      0.785158
                                 [山崎の戦い]      0.783573
                              [伏見城の戦い]      0.776076
                                    [奥州合戦]      0.773755
                                    [島原の乱]      0.771204
                              [沖田畷の戦い]      0.769704
                              [一ノ谷の戦い]      0.768712
                                 [厳島の戦い]      0.764277
                        [月山富田城の戦い]      0.763847
                     [天王寺・岡山の戦い]      0.762107
                                    [三木合戦]      0.758249
                        [鳥羽・伏見の戦い]      0.755719
                              [九戸政実の乱]      0.755190
                                    [会津戦争]      0.750164
                                    [石山合戦]      0.749404
                              [慶長出羽合戦]      0.746751
                                    [戊辰戦争]      0.745568
                                    [上杉景勝]      0.745289
                                    [紀州征伐]      0.744768
                                    [四国攻め]      0.744572
                                    [慶長の役]      0.742148
                                    [会津征伐]      0.740837
Enter word or sentence (EXIT to break): [真田丸の戦い]

Word: [真田丸の戦い]  Position in vocabulary: 454193

                                              Word       Cosine distance
------------------------------------------------------------------------
                              [一言坂の戦い]      0.688066
                              [七尾城の戦い]      0.687602
                              [手取川の戦い]      0.675760
                                 [粟津の戦い]      0.672440
                        [野田・福島の戦い]      0.661199
                           [信貴山城の戦い]      0.659862
                              [ドゥナの戦い]      0.655745
                                 [須々万沼城]      0.653602
                              [野良田の戦い]      0.652459
                              [南原城の戦い]      0.652226
                    [天王寺の戦い_(1576年)]     0.650776
                              [岩村城の戦い]      0.650516
                  [喜連川五月女坂の戦い]      0.648556
                                 [稲生の戦い]      0.647997
                     [ラールソートの戦い]      0.646685
                              [末森城の戦い]      0.646324
                           [伊集院頼久の乱]      0.645752
                                    [大崎合戦]      0.645676
                                    [堅田合戦]      0.645293
                                 [赤塚の戦い]      0.645071
                              [村木砦の戦い]      0.643145
                              [布部山の戦い]      0.642102
                           [稲葉山城の戦い]      0.640575
                                 [上京の戦い]      0.640390
                                 [六羽川合戦]      0.639293
                                    [上村合戦]      0.638805
                                    [横田高松]      0.637634
                              [太平寺の戦い]      0.637571
                              [中尾城の戦い]      0.636992
                              [尻垂坂の戦い]      0.635935
                                 [三船山合戦]      0.635824
                                 [蟹江城合戦]      0.634581
                                 [忍城の戦い]      0.633908
                              [立河原の戦い]      0.633376
                        [有田中井手の戦い]      0.633342
                        [八尾・若江の戦い]      0.632827
                              [高屋城の戦い]      0.631499
                  [野田城・福島城の戦い]      0.631484
                              [魚津城の戦い]      0.631203
           [スモレンスクの戦い_(1941年)]     0.629926
Enter word or sentence (EXIT to break):EXIT
$ 

*1:彼の名前でググったところ、Quora で次のような質問を見つけました。

www.quora.com

で、返答を読んでいると、ミコロフと面識のある人から次のようなコメントが出ていました。

I had the chance to talk to him once, and he spoke a bit about Google. One thing he criticized what that Google was too shy to use his models in production. For example Mikolov thought that word embeddings could be used in Google translate to propose other translations. But Google was worried about proposing wrong translation.

私は一度彼と話す機会があり、彼はGoogleについて少し話しました。 彼が批判していたことの一つは、Googleが彼のモデルを本番で使うのを恥ずかしがっていることでした。 例えば、ミコロフはGoogle翻訳で単語のエンベッディングを使えば、他の翻訳を提案できると考えていました。 しかし、Googleは間違った翻訳を提案することを心配していました。

つまり、Word2Vec がアルゴリズムとしてはあまりに単純すぎるし、 このアルゴリズムが「何故そのような(好ましい)結果を出すのか?」 ミコロフ自身にも説明できなかったことを Google は重くみていたようですね。

*2:このベクトル生成には機械学習が使われているそうです。 word2vecではどのような学習が行われているのか解説する記事は多数ありますが、 次の記事が参考文献としてよく登場するように思います。

Word2Vec のニューラルネットワーク学習過程を理解する

word2vecのソースを読んでみた

絵で理解するWord2vecの仕組み

オプションにも登場する「ネガティブサンプリング」と 「階層型ソフトマックス」は機械学習の際の最適化手法なんだそうです。 これも深入りすると二度と再浮上できそうにないので本項では割愛します(笑)

それから機械学習なのに GPGPUなんぞを使わなくとも それなりの時間で計算できるのが word2vec の特徴のひとつなんだそうです。詳細は 「word2vec の各種実装の速度比較」 をご覧ください。

*3:日本語文の場合、 この「わかち書き」によって word2vec の精度が変わってくるそうです。 詳細は 「学習済み日本語word2vecとその評価について」 で解説されています。気になる方はご一読を。

*4:同様のニーズは日本語文でも発生します。

例えば固有表現の事例としてよく掲げられる「福島第一原子力発電所」を形態素解析すると 「[福島][第][一][原子][力][発電][所]」という7つの単語に分割されてしまいます。

これを1つの固有名詞として扱う需要はあると思うのですが、 そのような word2phrase の利用事例は今のところ見つけられていません。 論文「日本語単語ベクトルの構築とその評価」 において…

単語の分割単位の問題は、word2vec とともに公開されている word2phrase というツールを活用する方法や、 分割された単語の品詞に着目して一定のルールで単語を再結合する方法が考えられる。

と言う記述を発見しただけです。他にもっと一般的な解決策があるのかな?

*5:詳細は 「学習済み日本語word2vecとその評価について」 をご覧ください。

メモ:Word2Vec

下記は Google Code にアーカイブされているword2vecの オリジナル実装のドキュメント の全文および翻訳です。 ドキュメントというよりはメモといった性格の文書なんですが、 C言語で実装されたword2vecの基本コマンドと 付属するデモスクリプトについて簡単に解説しています。

僕がみる限り、 プログラム自体は古き良き(そして悪しき)UNIX C プログラムですね。 解説は別記事に書く予定。 しかし、いやぁ、もう、man page を書いてよ、本当に。


Tool for computing continuous distributed representations of words.

単語の連続的な分散表現を計算するためのツール。

Introduction(イントロダクション)

This tool provides an efficient implementation of the continuous bag-of-words and skip-gram architectures for computing vector representations of words. These representations can be subsequently used in many natural language processing applications and for further research.

このツールは、単語のベクトル表現を計算するための連続バッグオブワードおよびスキップグラムアーキテクチャの効率的な実装を提供します。これらの表現は、その後、多くの自然言語処理アプリケーションやさらなる研究に使用することができます。

Quick start(クイックスタート)

Download the code: svn checkout http://word2vec.googlecode.com/svn/trunk/ Run 'make' to compile word2vec tool Run the demo scripts: ./demo-word.sh and ./demo-phrases.sh For questions about the toolkit, see http://groups.google.com/group/word2vec-toolkit

  • コードをダウンロードする
  • 'make' を実行して word2vec ツールをコンパイルする
  • デモ スクリプトを実行する: ./demo-word.sh と ./demo-phrases.sh

ツールキットについての質問は http://groups.google.com/group/word2vec-toolkit を参照してください。

How does it work(どのように動作するのか)

The word2vec tool takes a text corpus as input and produces the word vectors as output. It first constructs a vocabulary from the training text data and then learns vector representation of words. The resulting word vector file can be used as features in many natural language processing and machine learning applications.

word2vecツールはテキストコーパスを入力とし、出力として単語ベクトルを生成します。まず、学習テキストデータから語彙を構築し、単語のベクトル表現を学習します。生成された単語ベクトルファイルは、多くの自然言語処理機械学習アプリケーションで特徴量として使用することができます。

A simple way to investigate the learned representations is to find the closest words for a user-specified word. The distance tool serves that purpose. For example, if you enter 'france', distance will display the most similar words and their distances to 'france', which should look like:

学習された表現を調べる簡単な方法は、ユーザが指定した単語に最も近い単語を見つけることです。distanceツールがその目的を果たします。例えば、'france'と入力すると、distanceは最も類似した単語と'france'までの距離を表示します。

Word Cosine distance

            spain          0.678515
          belgium          0.665923
      netherlands          0.652428
            italy          0.633130
      switzerland          0.622323
       luxembourg          0.610033
         portugal          0.577154
           russia          0.571507
          germany          0.563291
        catalonia          0.534176

There are two main learning algorithms in word2vec : continuous bag-of-words and continuous skip-gram. The switch -cbow allows the user to pick one of these learning algorithms. Both algorithms learn the representation of a word that is useful for prediction of other words in the sentence. These algorithms are described in detail in [1,2].

word2vecには主に2つの学習アルゴリズムがあります:連続的なbag-of-wordsと連続的なskip-gramです。スイッチ -cbow を使うと、これらの学習アルゴリズムのいずれかを選択することができます。どちらのアルゴリズムも、文中の他の単語を予測するのに有用な単語の表現を学習します。これらのアルゴリズムについては、[1,2]で詳しく説明されています。

Interesting properties of the word vectors(言葉のベクトルの面白い性質)

It was recently shown that the word vectors capture many linguistic regularities, for example vector operations vector('Paris') - vector('France') + vector('Italy') results in a vector that is very close to vector('Rome'), and vector('king') - vector('man') + vector('woman') is close to vector('queen') [3, 1]. You can try out a simple demo by running demo-analogy.sh.

例えば、ベクトル操作 vector('Paris') - vector('France') + vector('Italy') の結果は vector('Rome') に非常に近く、 vector('king') - vector('man') + vector('woman') は vector('Queen') に近いことが最近明らかになりました [3, 1]。demo-analogy.shを実行すると簡単なデモを試すことができます。

To observe strong regularities in the word vector space, it is needed to train the models on large data set, with sufficient vector dimensionality as shown in [1]. Using the word2vec tool, it is possible to train models on huge data sets (up to hundreds of billions of words).

単語ベクトル空間の強い規則性を観測するためには,[1]に示すように,十分なベクトル次元を持つ大規模なデータセットでモデルを学習する必要があります。word2vecツールを用いることで,数千億語にも及ぶ膨大なデータセットに対してモデルを学習することが可能となります。

From words to phrases and beyond(言葉からフレーズへ、そしてその先へ)

In certain applications, it is useful to have vector representation of larger pieces of text. For example, it is desirable to have only one vector for representing 'san francisco'. This can be achieved by pre-processing the training data set to form the phrases using the word2phrase tool, as is shown in the example script ./demo-phrases.sh. The example output with the closest tokens to 'san_francisco' looks like:

特定のアプリケーションでは、より大きなテキストの断片をベクトルで表現することが有用です。例えば、'san francisco'を表現するためのベクトルは1つだけであることが望ましいでしょう。これは、例のスクリプト./demo-phrases.shに示されているように、word2phraseツールを使用してフレーズを形成するために学習データセットを前処理することによって達成することができます。'san_francisco'に最も近いトークンを用いた出力例は以下のようになります。

Word Cosine distance 

      los_angeles          0.666175
      golden_gate          0.571522
          oakland          0.557521
       california          0.554623
        san_diego          0.534939
         pasadena          0.519115
          seattle          0.512098
            taiko          0.507570
          houston          0.499762
 chicago_illinois          0.491598

The linearity of the vector operations seems to weakly hold also for the addition of several vectors, so it is possible to add several word or phrase vectors to form representation of short sentences [2].

ベクトル演算の直線性は、複数のベクトルを追加した場合にも弱く保たれるようであり、複数の単語やフレーズのベクトルを追加して短文を表現することが可能です[2]。

How to measure quality of the word vectors(単語ベクトルの品質を測る方法)

Several factors influence the quality of the word vectors: * amount and quality of the training data * size of the vectors * training algorithm

学習データの量と質、ベクトルのサイズ、学習アルゴリズムなど、いくつかの要因が単語ベクトルの品質に影響を与えます。

The quality of the vectors is crucial for any application. However, exploration of different hyper-parameter settings for complex tasks might be too time demanding. Thus, we designed simple test sets that can be used to quickly evaluate the word vector quality.

ベクトルの品質はどのようなアプリケーションにとっても非常に重要です。しかし、複雑なタスクのために異なるハイパーパラメータ設定を探索するのは時間がかかりすぎるかもしれません。そこで、我々は、単語ベクトルの品質を迅速に評価するために使用できる簡単なテストセットを設計しました。

For the word relation test set described in [1], see ./demo-word-accuracy.sh, for the phrase relation test set described in [2], see ./demo-phrase-accuracy.sh. Note that the accuracy depends heavily on the amount of the training data; our best results for both test sets are above 70% accuracy with coverage close to 100%.

[1]で記述された単語関係のテストセットについては、./demo-word-accuracy.shを、[2]で記述されたフレーズ関係のテストセットについては、./demo-phase-accuracy.shを参照してください。精度は学習データの量に大きく依存することに注意してください。両方のテストセットでの最良の結果は、カバレッジが100%に近い70%以上の精度です。

Word clustering(単語クラスタリング

The word vectors can be also used for deriving word classes from huge data sets. This is achieved by performing K-means clustering on top of the word vectors. The script that demonstrates this is ./demo-classes.sh. The output is a vocabulary file with words and their corresponding class IDs, such as:

単語ベクトルは、巨大なデータセットから単語クラスを導出するためにも使用することができます。これは、単語ベクトルの上でK-meansクラスタリングを実行することで達成されます。これを実演するスクリプトは ./demo-classes.sh です。出力されるのは、単語とそれに対応するクラスIDを持つボキャブラリーファイルです。

carnivores 234 
carnivorous 234 
cetaceans 234 
cormorant 234 
coyotes 234 
crocodile 234 
crocodiles 234 
crustaceans 234 
cultivated 234 
danios 234 
. . . 
acceptance 412 
argue 412 
argues 412 
arguing 412 
argument 412 
arguments 412 
belief 412 
believe 412 
challenge 412 
claim 412

Performance(パフォーマンス)

The training speed can be significantly improved by using parallel training on multiple-CPU machine (use the switch '-threads N'). The hyper-parameter choice is crucial for performance (both speed and accuracy), however varies for different applications. The main choices to make are:

学習速度は,複数のCPUで並列学習を行うことで大幅に向上します(スイッチ'-threads N'を使用).ハイパーパラメータの選択は性能(速度と精度の両方)に重要ですが、アプリケーションによって異なります。主な選択は以下の通りです。

  • architecture: skip-gram (slower, better for infrequent words) vs CBOW (fast)
  • the training algorithm: hierarchical softmax (better for infrequent words) vs negative sampling (better for frequent words, better with low dimensional vectors)
  • sub-sampling of frequent words: can improve both accuracy and speed for large data sets (useful values are in range 1e-3 to 1e-5)
  • dimensionality of the word vectors: usually more is better, but not always
  • context (window) size: for skip-gram usually around 10, for CBOW around 5

  • アーキテクチャ: スキップグラム (遅い、頻度の低い単語に適している) vs CBOW (速い)

  • 学習アルゴリズム:階層的ソフトマックス(頻度の低い単語に対してより良い) vs ネガティブサンプリング(頻度の高い単語に対してより良い,低次元ベクトルに対してより良い).
  • 頻出語のサブサンプリング: 大規模データセットの精度と速度を向上させることができます(有用な値は1e-3から1e-5の範囲です)
  • 単語ベクトルの次元性: 通常は多ければ多いほど良いが、必ずしもそうとは限らない
  • コンテキスト (ウィンドウ) サイズ: スキップグラムでは通常約10、CBOWでは約5

Where to obtain the training data(トレーニングデータの入手先)

The quality of the word vectors increases significantly with amount of the training data. For research purposes, you can consider using data sets that are available on-line:

単語ベクトルの品質は、学習データの量によって著しく向上します。研究目的のために、オンラインで利用可能なデータセットの利用を検討することができます。

Pre-trained word and phrase vectors(予め学習された単語とフレーズのベクトル)

We are publishing pre-trained vectors trained on part of Google News dataset (about 100 billion words). The model contains 300-dimensional vectors for 3 million words and phrases. The phrases were obtained using a simple data-driven approach described in [2]. The archive is available here: GoogleNews-vectors-negative300.bin.gz.

Google Newsのデータセット(約1,000億語)の一部で訓練された事前訓練済みのベクトルを公開しています。モデルには300万語の単語とフレーズの300次元ベクトルが含まれています。フレーズは、[2]で説明したシンプルなデータ駆動型のアプローチを用いて取得しました。アーカイブはこちらからご覧いただけます。GoogleNews-vectors-negative300.bin.gz

An example output of ./distance GoogleNews-vectors-negative300.bin:

./distance GoogleNews-vectors-negative300.binの出力例。

Enter word or sentence (EXIT to break): Chinese river

Word Cosine distance 

   Yangtze_River            0.667376
         Yangtze            0.644091
  Qiantang_River            0.632979
Yangtze_tributary           0.623527 
Xiangjiang_River            0.615482 
Huangpu_River               0.604726 
Hanjiang_River              0.598110 
Yangtze_river               0.597621 
Hongze_Lake                 0.594108 
Yangtse                     0.593442 

The above example will average vectors for words 'Chinese' and 'river' and will return the closest neighbors to the resulting vector. More examples that demonstrate results of vector addition are presented in [2]. Note that more precise and disambiguated entity vectors can be found in the following dataset that uses Freebase naming.

上記の例では、単語'Chinese'と単語'river'のベクトルを平均化し、結果として得られたベクトルに最も近い隣人を返す。ベクトル加算の結果を示すより多くの例は、[2]で紹介されています。より正確で曖昧性のない実体ベクトルは、Freebaseネーミングを使用している以下のデータセットにあることに注意してください。

Pre-trained entity vectors with Freebase naming(Freebaseネーミングを用いた事前学習済みのエンティティベクター

We are also offering more than 1.4M pre-trained entity vectors with naming from Freebase. This is especially helpful for projects related to knowledge mining.

また、Freebaseからネーミング付きの事前学習済みエンティティベクターを140万個以上提供しています。特にナレッジマイニング関連のプロジェクトに役立ちます。

Entity vectors trained on 100B words from various news articles: freebase-vectors-skipgram1000.bin.gz Entity vectors trained on 100B words from various news articles, using the deprecated /en/ naming (more easily readable); the vectors are sorted by frequency: freebase-vectors-skipgram1000-en.bin.gz Here is an example output of ./distance freebase-vectors-skipgram1000-en.bin:

様々なニュース記事から得られた 100B 単語を用いて学習されたエンティティベクタ: freebase-vectors-skipgram1000.bin.gz 様々なニュース記事から得られた 100B の単語に対して,非推奨の /en/ ネーミング(より読みやすい)を用いて学習されたエンティティベクタ: 頻度でソートされています: freebase-vectors-skipgram1000-en.bin.gz 以下は,./distance freebase-vectors-skipgram1000-en.bin の出力例です.

Enter word or sentence (EXIT to break): /en/geoffrey_hinton

Word Cosine distance 

/en/marvin_minsky           0.457204
/en/paul_corkum             0.443342
/en/william_richard_peltier 0.432396 
/en/brenda_milner           0.430886 
/en/john_charles_polanyi    0.419538 
/en/leslie_valiant          0.416399 
/en/hava_siegelmann         0.411895 
/en/hans_moravec            0.406726 
/en/david_rumelhart         0.405275 
/en/godel_prize             0.405176 

Final words(最後の言葉)

Thank you for trying out this toolkit, and do not forget to let us know when you obtain some amazing results! We hope that the distributed representations will significantly improve the state of the art in NLP.

このツールキットを試していただきありがとうございます。私たちは、分散表現がNLPの技術を大幅に向上させることを期待しています。

References(参考文献)

[1] Tomas Mikolov, Kai Chen, Greg Corrado, and Jeffrey Dean.
Efficient Estimation of Word Representations in Vector Space.
In Proceedings of Workshop at ICLR, 2013.

[2] Tomas Mikolov, Ilya Sutskever, Kai Chen, Greg Corrado, and Jeffrey Dean.
Distributed Representations of Words and Phrases and their Compositionality.
In Proceedings of NIPS, 2013.

[3] Tomas Mikolov, Wen-tau Yih, and Geoffrey Zweig.
Linguistic Regularities in Continuous Space Word Representations.
In Proceedings of NAACL HLT, 2013.

Other useful links(その他の有用なリンク)

Feel free to send us a link to your project or research paper related to word2vec that you think will be useful or interesting for the others.

word2vecに関連したプロジェクトや研究論文など、他の方の参考になると思われるものがありましたら、ご自由にリンクを送ってください。

Tomas Mikolov, Quoc V. Le and Ilya Sutskever.
Exploiting Similarities among Languages for Machine Translation.
We show how the word vectors can be applied to machine translation. Code for improved version from Georgiana Dinu here.

Word2vec in Python
by Radim Rehurek in gensim (plus tutorial and demo that uses the above model trained on Google News).

Word2vec in Java as part of the deeplearning4j project.
Another Java version from Medallia here.

Word2vec implementation in Spark MLlib.

Comparison with traditional count-based vectors and cbow model trained on a different corpus by CIMEC UNITN.

Link to slides about word vectors from NIPS 2013 Deep Learning Workshop: NNforText.pdf

Disclaimer(免責事項)

This open source project is NOT a Google product, and is released for research purposes only.

このオープンソースプロジェクトはGoogleの製品ではなく、研究目的でのみ公開されています。

IMAKITA for nodejs

IMAKITA for nodejs


2020/09/27(初版)
2021/01/05(改訂)
藤田昭人


対話機能の話、まずは道具立てから…

もちろん「書籍やブログと(擬似的に)対話する」がコンセプトのBookBotの場合、 返事はできるだけ書籍からの文章をそのまま使いたいところです。 書籍の中から話しかけられた質問に答えている箇所(たぶん、どこかの文節になると思いますが)を探し出せたとして、 それをギュッと一言にまとめる自動要約の機能が必要になります。

本稿ではそのためのアルゴリズのIMAKITAを紹介します。


抽出型文書要約アルゴリズム IMAKITA

この名前、日本語の音感を感じるので、最初は「なんの語呂合わせ?」と思ったのですが、 Importance Aligned Key Iterative Algorithm (IMAKITA) と言うことのようです。

この自動要約アルゴリズムGIGAZINEの次の記事を見つけました。 これは日本語文の要約結果が日本語として整っていることを期待して 「日本人が開発した自動要約アルゴリズム」を条件に探しまくった結果です。

gigazine.net

この記事、用例として「くろがね四起」だとか「松浦雅也*1だとかが出てくるので、 「これならオタク的文章でもいけるかも…」と考えた次第です。

開発者が書いたショートメモはこちら。 オリジナルは Python バージョンだったようです。

qhapaq.hatenablog.com

…が、その後 Chrome 拡張バージョンも公開されました。

qhapaq.hatenablog.com

こっちはもちろん JavaScript で書かれていますし、ソースも公開されています。 ソースを見るとこのバージョンは日本語と英語に対応しているみたいです。 論文によれば、自動要約には 抽出的要約(extractive summarization)と 抽象的要約(abstractive summarization)があるそうでが、 IMAKITAは抽出的要約を採用しているので、 マルチリンガルに(容易に)対応できるとのこと。

本稿では、この IMAKITA on Chrome をベースに以降の話を進めます。


IMAKITA for nodejs

もちろん Chrome 拡張された IMAKITA は便利なんですが、 このままでは nodejs では使えないのでヘッド部分を nodejs 用に書き換えました。 以下の手順でオリジナルコードにパッチを当てることができます。

1.IMAKITA on Chrome のコードをダウンロードする

GithubIMAKITA on Chrome のページからコードをダウンロードします。 Download ZIP が便利。

2.ソースファイルを取り出す

ダウンロードしたコードから、 preproc_ja.jsimakita_body.jstiny_segmenter-0.2.js を取り出します。

3.ソースファイルにパッチを当てる

preproc_ja.jsimakita_body.js については nodejs 向けに修正が必要です。
次のパッチを当ててください。

$ diff -u imakita-master/preproc_ja.js imakita4nodejs/preproc_ja.js
--- imakita-master/preproc_ja.js    2019-05-22 21:02:31.000000000 +0900
+++ imakita4nodejs/preproc_ja.js    2021-01-06 08:15:08.000000000 +0900
@@ -5,6 +5,9 @@
    m[a] = (m[a] || []).concat(i);
    return m;
     }, {});
+
+  const TinySegmenter = require('./tiny_segmenter-0.2');
+  const imakita = require('./imakita_body');

     var segmenter = new TinySegmenter();
     var word_weight = {};
@@ -24,7 +27,7 @@

     for(var i=0; i<sentences.length; ++i){
    var word_list_temp = segmenter.segment(sentences[i]);
-   console.log(word_list_temp);
+   //console.log(word_list_temp);
    var sens = {"id":i, "word_list":[], "importance":0};
    for (var j=0; j< word_list_temp.length; ++j){
        if (!(word_list_temp[j].toLowerCase() in not_word)){
@@ -42,13 +45,13 @@
    sens_list.push(sens);
     }

-    set_importance(sens_list, word_weight, word_list_minimum);
+    imakita.set_importance(sens_list, word_weight, word_list_minimum);
     /*
     console.log(sentences);
     console.log(sens_list);
     console.log(word_weight);
     */
-    var summary_id = binary_search(sens_list, word_weight, summary_number, 5);
+    var summary_id = imakita.binary_search(sens_list, word_weight, summary_number, 5);
     //console.log(summary_id);
     var summary_txt = []
     for (var i=0; i<summary_id.length; ++i){
@@ -58,3 +61,4 @@
     return summary_txt;
 }

+module.exports = preproc_ja;
$ diff -u imakita-master/imakita_body.js imakita4nodejs/imakita_body.js
--- imakita-master/imakita_body.js  2019-05-22 21:02:31.000000000 +0900
+++ imakita4nodejs/imakita_body.js  2021-01-05 11:56:23.000000000 +0900
@@ -114,3 +114,8 @@
     });

 }
+
+module.exports = {
+  binary_search:  binary_search,
+  set_importance: set_importance
+}
$ 


IMAKITA for nodejs を試してみる

サンプルプログラムを示します。テストデータは 「対話分野の研究をしたいときに気をつけたいこと」 から冒頭部分を借用しました。

var sections = [
  '身近なものに対話システムを組み込もうとする流れが来ているのか、機械学習の性能向上や、データの充実などの研究地盤が整ってきたのか、対話システムの研究が暖かみを帯びてきました。\n私はここ数年対話系の研究面白そうだなぁとなり、昨年度辺りから(突発的に卒業研究をしなければならないという話もあり)論文読みや実験を行っているのですが、この研究・発表など大変苦労したので、同じ轍を踏まないようにここに色々書いておこうと思います。\n尚、この分野は皆さんが想像している以上に広いので、どんなに頑張ってもこの手の解説は一説の域を出ることができないことを補足しておきます。(逆に、これが対話分野の研究のすべてだ!と言っているテキストなんかは注意したほうが良いと思います。自分は大分痛い目を見ました。)'
];


const preproc_ja = require('./preproc_ja');

var summary_number = 3;
var minimum_length = 10;
var separator = [". 。 .","\n . 。 ."];

for (var i = 0; i < sections.length; i++) {
  var summary = [];
  summary = preproc_ja(sections[i],
               summary_number,
               minimum_length,
               separator);
  console.log("------\n%s\n------\n", sections[i]);
  for (var j = 0; j < summary.length; j++) {
    console.log("\n%d: %s。", j, summary[j]);
  }
}

preproc_jaの引数の意味は次のとおりです。

引数 説明
(text) 要約対象の文章
summary_number 生成する要約文の数
minimum_length 要約文の最小文字数
separator 文を区切る文字

このプログラムを実行すると次のような結果が表示されます。

$ node summarization.js
------
身近なものに対話システムを組み込もうとする流れが来ているのか、機械学習の性能向上や、データの充実などの研究地盤が整ってきたのか、対話システムの研究が暖かみを帯びてきました。
私はここ数年対話系の研究面白そうだなぁとなり、昨年度辺りから(突発的に卒業研究をしなければならないという話もあり)論文読みや実験を行っているのですが、この研究・発表など大変苦労したので、同じ轍を踏まないようにここに色々書いておこうと思います。
尚、この分野は皆さんが想像している以上に広いので、どんなに頑張ってもこの手の解説は一説の域を出ることができないことを補足しておきます。(逆に、これが対話分野の研究のすべてだ!と言っているテキストなんかは注意したほうが良いと思います。自分は大分痛い目を見ました。)
------


0: 身近なものに対話システムを組み込もうとする流れが来ているのか、機械学習の性能向上や、データの充実などの研究地盤が整ってきたのか、対話システムの研究が暖かみを帯びてきました。

1: 尚、この分野は皆さんが想像している以上に広いので、どんなに頑張ってもこの手の解説は一説の域を出ることができないことを補足しておきます。

2: (逆に、これが対話分野の研究のすべてだ!と言っているテキストなんかは注意したほうが良いと思います。
$

5行の文章を3行に要約するのでは意味がないので summary_number を1にしてみたら…

$ node summarization.js
------
身近なものに対話システムを組み込もうとする流れが来ているのか、機械学習の性能向上や、データの充実などの研究地盤が整ってきたのか、対話システムの研究が暖かみを帯びてきました。
私はここ数年対話系の研究面白そうだなぁとなり、昨年度辺りから(突発的に卒業研究をしなければならないという話もあり)論文読みや実験を行っているのですが、この研究・発表など大変苦労したので、同じ轍を踏まないようにここに色々書いておこうと思います。
尚、この分野は皆さんが想像している以上に広いので、どんなに頑張ってもこの手の解説は一説の域を出ることができないことを補足しておきます。(逆に、これが対話分野の研究のすべてだ!と言っているテキストなんかは注意したほうが良いと思います。自分は大分痛い目を見ました。)
------


0: (逆に、これが対話分野の研究のすべてだ!と言っているテキストなんかは注意したほうが良いと思います。
$

ちゃんと一番大事な文が残りました。


さいごに

無作為に選んだ文章を、 まるでその意味を理解しているかのように要約するIMAKITAの実力に 正直ちょっとビックリしたのですが…

これがWord2vecに代表されるベクトル空間モデルのトリックなんでしょうね。

種明かしは、いずれまた…

以上

*1:松浦雅也の紹介で PSY・S が出てこないってどう言うこと?

BookBot: Chatbot for reading Book

BookBot:読書のためのチャットボット(英訳)


2020/1/1
Akito Fujita


This document has been translated from the Japanese version using DeepL.


It's been a very long time.
What have I been doing after four months of silence?
...I was making this.

bookbot.glitch.me

Contrary to many people's expectations, it is a pure JavaScript web application. In this article, I will give an overview of this system, BookBot.


BookBot: Chatbot for reading Book

BookBot, as the name suggests, is a chatbot for reading, and it aims to be a chatbot that users can chat with about books. This idea came about when I was talking about chatbots with a close friend of mine, who seems to be a prolific reader, and he said, "If the book itself talks about what it says, it saves me the trouble of reading.

In fact, when we try to use machine learning techniques to develop a dialogue system, the dialogue corpus becomes a problem. A dialogue corpus is a pair of questions and answers that must be prepared as training data. It is said that in order for a dialogue system using machine learning to be able to carry out a reasonable conversation, about 100,000 question-answer pairs must be collected as training data. Such a long dialogue record is not very common.

However, if you want to prepare a new corpus of dialogues manually to develop a chatbot, you don't need such a large amount of dialogues. According to a New York Times interview with Loebner Prize winner Richard Wallace, in the early development of Alicebot, the bot was first piloted in an office, and Wallace filled in each unanswered question from users (in the office). That's right.

If he taught Alice a new response every time he saw it baffled by a question, he would eventually cover all the common utterances and even many unusual ones. Wallace figured the magic number was about 40,000 responses. Once Alice had that many preprogrammed statements, it -- or ''she,'' as he'd begun to call the program fondly -- would be able to respond to 95 percent of what people were saying to her.

He says that if you assume a chatbot's personality and prepare appropriate response text for it, the chatbot will be able to respond appropriately without using machine learning techniques. In other words, "We have failed to master human conversation, not because we are too complex, but because we are too simple. This is an insight that Richard Wallace has discovered.

However, "manually inputting 40,000 questions and responses"...

...and that's when my friend's idea of "personifying books" came into my mind.

To begin with, books (and blogs, for that matter) are the author's own statement. If you look for a chatbot response in a book, that character becomes the author's own character. And if the author has written a significant amount of text, wouldn't he be able to find the most appropriate sentence for any given question without having to write up a new response?

In the end, his comment led me to the idea of using the original text of books as a dialogue corpus. In a nutshell, the idea is to create a pseudo-dialogue corpus by applying information retrieval techniques.


System Overview

Here is a system that is already in place.

The first platform is Glitch. Basically, it is a typical web application using node.js + express.js JavaScript. It is based on hello-express and hello-sqlite provided by Glitch, so the basic structure has been retained. I also used BotUI because I wanted a chatbot-like UI*1, and it made it look like a chatbot.

Unlike most chatbots, BookBot starts out as a book browser. The startup screen looks like this.


f:id:Akito_Fujita:20201228103437p:plain
BookBot

Thanks to BotUI, when you view it in your phone's web browser, it looks like a running phone app. You can then use BotUI's button feature to navigate to the next page, previous page, or previous/next heading.


Blog posts currently included in the collection

Unfortunately, it's still a prototype at the moment, so you can only view blog posts I've written in the past. At the moment, I have the following seven included.

I'm sorry. It's all written in Japanese.

Chapter Title
1 When did AI (Artificial Intelligence) start? The Inside Story of the Dartmouth Conference
2 Do you know what Artificial Brainlessness is?
3 ELIZA (1) What is a program that "mimics listening"?
4 ELIZA (2) What is Client-Centered Therapy?
5 ELIZA (3) Scripting - A mechanism to create a response
6 ELIZA (4) DOCTOR Scripts
7 ELIZA (5) What is the background of the development?

I have about 30 blog posts, and I plan to add more as needed. I also plan to implement a function to call the chatbot on any page.


Script generation from Markdown documents

I spent a lot of time designing the script for BookBot. In conclusion, I decided to use a flat structure based on the original article's chapter structure, or "chain of sections". The main reason for this is that it is "mechanically transformable from the original article", but it is also heavily influenced by Weizenbaum's second paper.*1

For now, I'm assuming that articles are written in Markdown, and I've written a program to automatically generate BookBot scripts from Markdown documents. For interpreting Markdown, I used 'Remark', which I introduced earlier. The source of the module used for interpretation, parse-markdown.js, is attached at the end.

The module parseMarkdown expands the Markdown document into a syntax tree called Markdown Abstract Syntax Tree(MDAST), but there are two plugins to keep in mind.

One is 'remark-gfm'. remark's Markdown parser interprets Markdown documents based on CommonMark Spec, but this plugin also interprets Markdown used on Github, i.e. GitHub Flavored Markdown Spec.

The other is 'remark-footnotes', a plugin that supports the following three types of footnote notation in Markdown.

Here is a footnote reference,[^1]
another,[^longnote],
and optionally there are inline
notes.^[you can type them inline, which may be easier, since you don’t
have to pick an identifier and move down to type the note.]

[^1]: Here is the footnote.

[^longnote]: Here’s one with multiple blocks.

    Subsequent paragraphs are indented to show that they
belong to the previous footnote.

        { some.code }

    The whole paragraph can be indented, or just the first
    line.  In this way, multi-paragraph footnotes work like
    multi-paragraph list items.

This paragraph won’t be part of the note, because it
isn’t indented.


To create a glossary

The underlined part of the response from BookBot is a link. For example, when you see the following...

f:id:Akito_Fujita:20201228125712p:plain
Before Click

Click on the underlined "smart speaker"...

f:id:Akito_Fujita:20201228125744p:plain
After Click

...and a description of the "smart speaker" will be displayed.

I'm actually using Markdown's Link Reference Definition for this. (I think it's called "indirect link" in Japanese?).

Example 161

[foo]: /url "title"

[foo]

...is the format, but BookBot uses "title" for "glossary" as shown below.

[スマートスピーカー]: https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC "用語: スマートスピーカー(英: Smart Speaker)とは、対話型の音声操作に対応したAIアシスタント機能を持つスピーカー。内蔵されているマイクで音声を認識し、情報の検索や連携家電の操作を行う。日本ではAIスピーカーとも呼ばれる。"

The URL in this notation is a link to Wikipedia, and the "Glossary" contains the first sentence of the Wikipedia page.

Note that "term" at the top of the "glossary" is a category that is aware of the Named-entity tag. This link definition is not displayed when Markdown is converted to HTML or PDF, but the term is linked to. For example, if you write the following in Markdown...

突如、[人工無脳][]に執着し始めた理由は[スマートスピーカー][]の登場でした。

If you open it in a normal browser, it will look like this

突如、人工無脳に執着し始めた理由はスマートスピーカーの登場でした。

In the case of BookBot, it displays the "term description" written in the title.

There is another use for this Link Reference Definition, for example, if you write the following in Markdown...

[Alexa][スマートスピーカー]のアプリ([スキル][]って言うのかな?)の開発者の世代では「対話」と言うとチャットボットのイメージが立ち上がるのでしょうか?

In the browser...

Alexaのアプリ(スキルって言うのかな?)の開発者の世代では「対話」と言うとチャットボットのイメージが立ち上がるのでしょうか?

...and it only shows "Alexa", but clicking on it will take you to the Wikipedia page on "smart speakers". In fact, BookBot uses this notation to define synonyms. In this way, BookBot uses a little trick to create a glossary.


Future work

The development of BookBot is about to reach its climax since it lacks the essential dialogue function.

I would like to start by investigating word2vec.

parse-markdown.js

'use strict'

var vfile = require('to-vfile');
var report = require('vfile-reporter');
var unified = require('unified');
var parse = require('remark-parse');
var gfm = require('remark-gfm');
var footnotes = require('remark-footnotes');


// rmposition -- ポジションデータを削除する

function rmposition() {
  return transformer;

  function transformer(tree, file) {
    traverse(tree);
  }

  function traverse(tree) {
    if (tree.position) delete tree.position;
    if (!tree.children) return
    for (var i = 0; i < tree.children.length; i++) {
      traverse(tree.children[i]);
    }
  }
}


// pluginCompile -- 終端処理

function pluginCompile() {
  this.Compiler = compiler;

  function compiler(tree) {
    //console.log(tree);
    return(tree);
  }
}


function parseMarkdown(name) {
  var processor = unified()
      .use(parse)
      .use(gfm)
      .use(footnotes, {inlineNotes: true})
      .use(rmposition)
      .use(pluginCompile);

  //console.log("# %s", name);
  processor.process(vfile.readSync(name), done);

  var result;
  function done(err, file) {
    //console.error(report(err || file));
    //console.log(file.result);
    result = file.result;
  }

  return(result);
}

module.exports = parseMarkdown;

*1:In his second paper, Weizenbaum examines the structure of conversation by comparing "cocktail party chatter" with "discussion between two physicists".

Cocktail party chatter, for example, has a rather straight line character. Context is constantly being changed -- there is considerable chaining of nodes -- but there is hardly any reversal of direction along already established structure. The conversation is inconsequential in that nothing being said has any effect on any questions raised on a higher level. Contrast this with a discussion between, say, two physicists trying to come to understand the results of some experiment. Their conversation tree would be not only deep but broad as well, i.e., they would ascend to an earlier contextual level in order to generate new nodes from there. The signal that their conversation terminated successfully might well be that they ascended (back to) the original node, i.e., that they are again talking about what they started to discuss.

This thought has inspired me to spend a lot of time thinking about the "conversation of reading. I have spent a lot of time thinking about the "conversation of reading. For example, if we think of reading as an activity to gain new knowledge, we can replace it with a conversation between two people with different levels of knowledge, a conversation between a teacher and a student. (A typical example is the so-called "Zen conversation"). In this case, the conversation proceeds based on the teacher's knowledge. Unlike a "cocktail party chat," the conversation is not aimless, but the level of the student's knowledge is taken into account, so it is not as broad or deep as a "discussion between two physicists.

Also, in the case of reading, the knowledge of the higher level speaker is static (not a line of dialogue spun up on the spot), so if you call the act a conversation, it is characterized by less divergence than others. I believe that authors of books make deliberate decisions on how to organize their chapters in order to promote reader understanding, but since the knowledge level of readers is not as flat as authors assume (they know this, but not that), they may not always follow the table of contents. It is also important to take into account the so-called reader's guide.

As I began to think about how to structure the script so that it would be able to handle these problems without difficulty, I found that I could not find the best solution. In the end, I decided to follow the principle of how most people read, i.e., the chapter structure of a book, and assume that if you don't understand the passage (section) you are reading at the time, you will jump to another passage (section) in the book that describes it.

In terms of response generation, I felt the need to be able to set up a combined strategy for each clause (section), such as rule-based like ELIZA, machine generation by machine learning, and procedural response by automaton. This idea is also an extension of the "hierarchy of scripts" proposed by Weisenbaum.

This implementation of the scripting area is still in flux, so we won't be actively releasing the code for the time being. If you want to see it, please use Glitch's editor to view the source with 'AS-IS'.

BookBot:読書のためのチャットボット

BookBot: Chatbot for reading Book


2020/12/31
藤田昭人


大変ご無沙汰してしまいました。
4ヶ月間も沈黙して何をしていたのか?
…というとこれを作ってました。

bookbot.glitch.me

多くの人の予想に反して pure JavaScript のウェブ・アプリケーションです。 本稿ではこのシステム、BookBot の概要を説明します。


BookBot:読書のためのチャットボット

BookBotはその名のとおり「読書のためのチャットボット」でして、 利用者が「書籍について雑談をすることができるチャットボット」を目指しています。 このアイデア、親しい友人とチャットボットについて話していたときに 「書籍自身が書いてあることをかいつまんで話してくれたら、読書する手間が省ける」 と多読家らしい彼が語った一言が「対話コーパスとして書籍原文を使う」キッカケになりました。

実際、対話システムの開発に機械学習の技術を活用しようとなると対話コーパスが問題になります。 質問文と回答文のペアを学習データとして用意しなければならない対話コーパス機械学習を使った対話システムがそれなりに会話が成立するようになるには、 学習データとして10万程度の質問文&回答文ペアを集めなければならないとか。 そんな長大な対話録はあまり見かけません。

しかし、チャットボットを開発するために 新たに人手で対話コーパスを用意する場合には、 そこまで大量の対話録は必要ないようです。 ローブナー賞の覇者である リチャード・ウォレスへのニューヨーク・タイムズのインタビューよれば、 Alicebot の初期の開発では、まずはオフィス内でボットを試験運用を始めて、 (オフィス内の)利用者からの答えられなかった質問には その都度ウォレスが回答文を埋めていったそうです。

もしアリスが質問で混乱しているのを見ればそのたびに、 彼がアリスに新しい答えを教えたとしたら、 彼は最終的には一般的な発言すべてをカバーするだけなく、 さらには多くの変わった発言もカバーするだろう。 ウォレスは、マジックナンバーは約4万件の応答だと考えている。 これだけプログラムされた発言ができるようになれば、 アリスに向かって(あるいは「彼女は」と呼びはじめたときには) 人々が言っていることの95%に反応できるようになる。

チャットボットの人格を想定し、 それに相応しい回答文を用意すれば 機械学習の技術を使わなくても チャットボットは相応の応答ができるようになるそうです。つまり 「人間の会話をマスターできなかったのは、人間が複雑すぎるからではなく、単純すぎるからだ」 という。これはリチャード・ウォレスが発見した見識でしょう。

とはいえ「手作業で4万件の質問&応答の入力する」というのはねぇ…

…とモヤモヤしていたところへ飛び込んできたのが 友人の一言の「書籍を擬人化する」アイデアだったのです。

そもそも書籍(ブログもそうですが)とは著者自身の主張でもあります。 もしチャットボットの応答を書籍の中から探せば、 そのキャラクターは著者自身のキャラクターとなります。 そして、著者がまとまった量の文章を書いておれば、 新たに応答文を書き起こさなくても 任意の質問にもっとも相応しい1文を見つけ出すこともできるのでは?

結局、彼の一言が「対話コーパスとして書籍原文を使う」という思いつきに結び付くことになりました。 一言で説明すれば「情報検索の技術を応用して擬似的な対話コーパスを作る」というアイデアです。


システムの概要

既に出来上がっているシステムを紹介します。

まずプラットホームはGlitchを使っています。 基本的にはnode.jsexpress.jsJavaScriptによる 典型的なウェブ・アプリケーションです。 Glitchが用意してくれるhello-expresshello-sqliteを ベースに作成しましたので、基本的な構造はそのまま踏襲しています。 それからチャットボット風のUIが欲しかったのでBotUIも使っています*1。おかげで、チャットボットらしい見た目になりました。

一般的なチャットボットとは異なり、 BookBotは最初は書籍ブラウザーとして 起動します。起動画面はこんな感じ。


f:id:Akito_Fujita:20201228103437p:plain
BookBot

BotUIのおかげで、 スマホのウェブ・ブラウザーで表示すると スマホ・アプリが動いているように見えます。 それからBotUIのボタン機能を使って 次ページや前ページ、前後の見出しに移動できます。


現在収録しているブログ記事

残念ながら、今のところまだプロトタイプなので 僕が過去に書いたブログ記事しか閲覧できません。 現時点で収録してるのは下記の7本です。

タイトル
AI(人工知能)っていつから始まったの? ー ダートマス会議の内幕
人工無脳って知ってる?
ELIZA(1)「傾聴」を模倣するプログラムとは?
ELIZA(2)クライアント中心療法とは?
ELIZA(3)スクリプト ー 応答を作り出す仕掛け
ELIZA(4)DOCTOR スクリプト
ELIZA(5)開発の背景は?

僕のブログ記事は30本ほどあるので、 随時追加していく予定です。 また、任意のページでチャットボットを呼び出す 機能を実装する予定です。(詳細は後述)


Markdownドキュメントからのスクリプト生成

BookBotスクリプトの設計にはかなり悶絶し、結果的長い時間を使ってしまいました。 結論としては元になる記事の章立て、 つまり「文節(セクション)の連鎖」に基づくフラットな構造を採用しました。 その最大の理由は、まず「元となる記事から機械的な変換が可能」であるということなのですけども、 それだけではなくワイゼンバウム第2論文 から大きな影響を受けています*2

今のところ、記事はMarkdownで記述されていることを仮定していて、 MarkdownドキュメントからBookBotスクリプトを自動生成するプログラムを書きました。 Markdown の解釈には、以前紹介した 'Remark' を使っています。解釈に使っているモジュール parse-markdown.js のソースを末尾に添付しておきます。

モジュール parseMarkdown はMarkdownドキュメントを Markdown Abstract Syntax Tree(MDAST) という構文木に展開してくれますが、ここで留意すべきプラグインが2つあります。

1つは 'remark-gfm' 。remark のMarkdownパーサーは CommonMark Spec に基づくMarkdownドキュメントを解釈してくれますが、 このプラグインを使うとGithubで使われているMarkdown、すなわち GitHub Flavored Markdown Spec まで解釈してくれます。

もうひとつは 'remark-footnotes' で、これはMarkdownでの次の3種類の フットノートの記法に対応するプラグインです。

Here is a footnote reference,[^1]
another,[^longnote],
and optionally there are inline
notes.^[you can type them inline, which may be easier, since you don’t
have to pick an identifier and move down to type the note.]

[^1]: Here is the footnote.

[^longnote]: Here’s one with multiple blocks.

    Subsequent paragraphs are indented to show that they
belong to the previous footnote.

        { some.code }

    The whole paragraph can be indented, or just the first
    line.  In this way, multi-paragraph footnotes work like
    multi-paragraph list items.

This paragraph won’t be part of the note, because it
isn’t indented.

まぁ、はてなのマークダウンの記法とはちょっと違うので、 そこは困りもんなんですけども。


用語集を作るために

ちなみに、BookBotからの応答の下線部分はリンクになっています。 例えば、次のように表示されている時に…

f:id:Akito_Fujita:20201228125712p:plain
クリック前

下線付きの「スマートスピーカー」をクリックすると…

f:id:Akito_Fujita:20201228125744p:plain
クリック後

…と「スマートスピーカー」の説明が表示されます。

これ、実はMarkdownLink Reference Definition (日本語では「間接リンク」というのかな?) を活用してます。

Example 161

[foo]: /url "title"

[foo]

…というのが書式なんですけども、 BookBotでは次のように「タイトル」を「用語解説」に使ってます。

[スマートスピーカー]: https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC "用語: スマートスピーカー(英: Smart Speaker)とは、対話型の音声操作に対応したAIアシスタント機能を持つスピーカー。内蔵されているマイクで音声を認識し、情報の検索や連携家電の操作を行う。日本ではAIスピーカーとも呼ばれる。"

この記法の URL は Wikipedia へのリンク、 「用語解説」には Wikipedia ページの冒頭の文言を納めています。

ちなみに「用語解説」の先頭にある「用語」というのは、 固有表現タグを意識したカテゴリです*3。 このリンク定義はMarkdownをHTMLやPDFに変換すると表示されず、 その用語にはリンクが貼られます。 例えばMarkdownで次のように記述すると…

突如、[人工無脳][]に執着し始めた理由は[スマートスピーカー][]の登場でした。

普通にブラウザーで開くと次のように表示されます。

突如、人工無脳に執着し始めた理由はスマートスピーカーの登場でした。

で「スマートスピーカー」をクリックすると該当するWikipediaページに移動しますが、 BookBot の場合はタイトルに書かれている「用語説明」を表示する訳です。

この Link Reference Definition にはもう1つ使い道があって、 例えばMarkdownで次のように記述すると…

[Alexa][スマートスピーカー]のアプリ([スキル][]って言うのかな?)の開発者の世代では「対話」と言うとチャットボットのイメージが立ち上がるのでしょうか?

ブラウザでは…

Alexaのアプリ(スキルって言うのかな?)の開発者の世代では「対話」と言うとチャットボットのイメージが立ち上がるのでしょうか?

…と「Alexa」としか表示されませんが、 クリックするとWikipediaの「スマートスピーカー」のページに移動します。 実はBookBotではこの記法を同義語の定義に使っています。 このようにBookBotは、 ちょっとしたトリックを使って用語集を作ってます。


これからの作業

肝心の対話機能がないのでBookBotの開発はこれからが佳境です。

いわゆるAIチャットボットと呼ばれるサービスは 既にたくさん存在していますが、 その核技術として頻繁に登場するのがword2vecです。 その仕組みについては次のようなドキュメントが幾つも見つかりますが…

qiita.com

そこで示される実装例はPythonだけで JavaScriptの実装例は皆無といった状況です。 JS版のword2vecというと下記のパッケージがありますが

www.npmjs.com

これはオリジナルの Google の word2vec にJS向けのラッパーを付けただけなので困りもんです。 また日本語文章にword2vecを適用する場合には、 固有の問題がある そうなので、これまた悩みどころです。

一方、対話機能の実装という観点では、最近良い教科書が出版されました。

www.ohmsha.co.jp

これまたPythonなんですけども、ソースをチラチラ見てると 「JavaScriptに書き直すのはそれほど難しくないのでは?」などと考えています。 (そういえば、昔JavaのコードをC++に書き直しながら 両方の言語を覚えるという無茶をしたなぁ…)

というわけで…

しばらくはBookBotネタでブログを書くことにします。

以上

parse-markdown.js

'use strict'

var vfile = require('to-vfile');
var report = require('vfile-reporter');
var unified = require('unified');
var parse = require('remark-parse');
var gfm = require('remark-gfm');
var footnotes = require('remark-footnotes');


// rmposition -- ポジションデータを削除する

function rmposition() {
  return transformer;

  function transformer(tree, file) {
    traverse(tree);
  }

  function traverse(tree) {
    if (tree.position) delete tree.position;
    if (!tree.children) return
    for (var i = 0; i < tree.children.length; i++) {
      traverse(tree.children[i]);
    }
  }
}


// pluginCompile -- 終端処理

function pluginCompile() {
  this.Compiler = compiler;

  function compiler(tree) {
    //console.log(tree);
    return(tree);
  }
}


function parseMarkdown(name) {
  var processor = unified()
      .use(parse)
      .use(gfm)
      .use(footnotes, {inlineNotes: true})
      .use(rmposition)
      .use(pluginCompile);

  //console.log("# %s", name);
  processor.process(vfile.readSync(name), done);

  var result;
  function done(err, file) {
    //console.error(report(err || file));
    //console.log(file.result);
    result = file.result;
  }

  return(result);
}

module.exports = parseMarkdown;

*1:この二つを手名付けるのに苦労しました(笑)
実は、いろいろ初めての経験が重なったため 開発は予定より2ヶ月遅れてしまいました。
BotUI の使い方、特に JavaScriptの async/await の使い方はそのうち書きます。

*2:ワイゼンバウム第2論文 で、会話の構造について「カクテルパーティでのおしゃべり」と 「二人の物理学者の議論」の比較で考察しています。

例えば、カクテルパーティーのおしゃべりは直線的な傾向があります。 コンテキストは絶えず変更されています(ノードにはかなりの連鎖があります)が、 すでに確立された構造に沿った方向の逆転はほとんどありません。 会話は、何も言われても、より高いレベルで提起された質問に影響を与えないという点で重要ではありません。 これを、たとえば、ある実験の結果を理解しようとする2人の物理学者間の議論と比較してみましょう。 彼らの会話ツリーは、深いだけでなく広いものになります。 つまり、そこから新しいノードを生成するために、以前のコンテキストレベルに上昇します。 会話が正常に終了したというシグナルは、元のノードに戻った(元に戻った)こと、 つまり、(最初に)話し始めた内容について再び話していることです。

この考察に触発されて「読書という会話」について何度となく考えることに、 多くの時間を費やしてしまいました。例えば、読書を「新たな知識を得る活動」と捉えると、 知識のレベルの異なる2者間、「先生と生徒の会話」に置き換えることできます (典型的な事例はいわゆる「禅問答」という会話ですね)。 この場合、会話は先生側の知識を前提に会話が進むことになります。 「カクテルパーティでのおしゃべり」とは違い無目的ではないですが、 生徒の知識レベルが考慮されるので 「2人の物理学者の議論」ほど広さや深さは自ずと抑制されます。

また読書の場合、レベルの高い方の話者の知識は静的である (その場で捻り出されるセリフではない) ことから、その行為を会話と呼ぶなら 他よりも発散する度合いは小さいことも特徴になります。 書籍の著者は読者の理解を促すために熟慮の上で章立てを決めていると思うのですが、 読者の知識レベルが著者が想定しているほどフラットではないので (これは知っているがこれは知らないといった) 目次どおりに読み進められるとは限らない、 いわゆる(ウェブ・ページではよく議論される)読者の導線も考慮しなければなりません。

こういった問題に無理なく対応できるスクリプトの構造を考え始めた結果、 ベストの解は見出せなくなってしまいました。 結局、多くの人の読書の導線、つまり書籍の章立てを原則とし、 その時に読んでいる文節(セクション)が理解できなければ、 それについて書いてある書籍の他の文節(セクション)にジャンプするのだろうと 仮定することにしました。

応答文生成に関しても、 ELIZAのようなルールベース、 機械学習による機械的な生成、 さらにオートマトンによる手順的な応答など 複合的な戦略を、 各文節(セクション)毎に設定できる必要を感じました。 これもワイゼンバウムが提案する 「スクリプトの階層化」の延長上にあるアイデアです。

この、スクリプト界隈の実装は仕様がまだまだ揺れそうなので、 当面コードを積極的に公開する事はなさそうです。 どうあってもみたい人は Glitch のエディターで ソースを 'AS-IS' で見てください。

*3:ここで「固有表現タグ」にニヤッと反応した方はわかってらっしゃる(笑)
そうでない方が、これあたりを読んでいただける良いかと思います。

heat02zero.hatenablog.com

自然言語処理の世界では IREX というのがあるんだそうで、 論文 も出てました。要は次のように固有名刺を分類するのだそうです。

略号 説明 用例 IPADIC
ART 固有物名 ノーベル文学賞Windows7 41
LOC 地名 アメリカ、千葉県 46, 47
ORG 組織 自民党NHK 45
PSN 人名 安倍晋三メルケル 42, 43, 44
DAT 日付 1月29日、2016/01/29
TIM 時間 午後三時、10:30
MNY 金額 241円、8ドル
PNT 割合 10%、3割

ちなみにIPADICでも、一部の固有名詞には ID が振られています。

固有表現とは「意味的な最小単位」で、 MeCab などで出力される「形態素」よりは少し大きい というのが僕の理解です(正確な定義はよくわからんが)。

例えば、普通の日本人なら「ウィーン」と言えば地名、 「アインシュタイン」と言えば人物名と直感的に認識しますよね? もしチャットボットに同じことをさせるためには、 単語の属性として固有表現タグを貼っておかなければならない訳です。

Remark(5)MeCab による日本語トークナイザー

parse-japanese-mecab: Japanese tokenizer with MeCab


2020/09/11
修正 2020/09/13
藤田昭人


2020/09/13 追記しました

ソースコード に誤りを見つけたため修正版に更新しました。 結局、nlcst 構文木は自前で構築することになりました。 すいません。

僕にとって JavaScript は5つ目のプログラミング言語なんですが、 相変わらず習得に苦労しています。 今回どハマりしているのは prototype です。 もちろん JavaScriptC++Java のような class による記法をサポートしているのですが、 困った事に remark / unified では prototype を使って クラスを実装しているので読み下すのにてこずってます。

この種のトラブルには「わかってしまえばなんて事ない」原因が付き物なのですけども、 それに気付くまでに費やす時間たるや… またもや「ほんと我ながら言語のセンスがない」ことを再確認させられています。 悪戦苦闘して作ったのは、 前回 紹介した parse-latin のラッパーのサブクラスのバージョンです。 コードの方は後ほど…

パーサーをプラグイン・チェーンに挿入するには

何故サブクラスのバージョンを作ったかというと、 プラグイン・チェーンに挿入できるパーサーにする必要があったからです。

remark には各トークンに品詞情報を付与するための retext-pos が用意されてますが、 これを呼び出すためには retextプラグイン・チェーンを構成する必要があります。 例えば、次のコードで構文木には品詞情報が埋め込まれます。

var unified = require('unified');
var japanese = require('./retext-wrapper');
var pos = require('retext-pos');
var stringify = require('retext-stringify');
var inspect = require('unist-util-inspect');


function plugin() {
  return(transformer);

  function transformer(tree) {
    //console.log(inspect(tree));
    console.log("{ type: '%s' }", tree.type);
    console.log("{ type: '%s' }", tree.children[0].type);
    console.log("{ type: '%s' }", tree.children[0].children[0].type);
    console.log(tree.children[0].children[0].children[0]);
    console.log(tree.children[0].children[0].children[1]);
    console.log(tree.children[0].children[0].children[2]);
    console.log(tree.children[0].children[0].children[3]);
    console.log(tree.children[0].children[0].children[4]);
    console.log(tree.children[0].children[0].children[5]);
  }
}


var str = "Professor Bob Fabry, of the University of California at Berkeley, was in attendance and immediately became interested in obtaining a copy of the system to experiment with at Berkeley.";

var processor = unified()
    .use(japanese)
    .use(pos)
    .use(plugin)
    .use(stringify);

processor.process(str, function (err) {
  if (err) {
    throw err;
  }
});

このコードで登場するプラグイン・バージョンの パーサー retext-wrapper は次のようなコードになります。 retext-english をほんの少しだけ修正しました。

'use strict'

var unherit  = require('unherit');
var Wrapper = require('./parse-wrapper');

module.exports = parse;
parse.Parser = Wrapper;

function parse() {
  this.Parser = unherit(Wrapper);
}

ここで 前回 紹介したラッパーバージョンのパーサー本体を使うと プラグイン・チェインの実行時のチェックにひっかかって異常終了します。 まるで「風が吹けば桶屋が儲かる」みたいにトラブルが出現しました(笑)


ラッパーからサブクラスへの書き直し

やむなく unified のコードを読んでみたところ、 パーサーはクラスとして実装されていることを仮定してるように見えました。 前回紹介したようにパーサーの核をなし nlcst 構文木を生成してくれる parse-latin のコードはそのまま残したいので、 次のようにそのサブクラスとしてラッパーを定義することにしました。

'use strict'

var ParseLatin = require('parse-latin');

// Inherit from `ParseLatin`.
ParseLatinPrototype.prototype = ParseLatin.prototype;

// Constructor to create a `ParseWrapper` prototype.
function ParseLatinPrototype() {
}

ParseWrapper.prototype = new ParseLatinPrototype();

// Transform Japanese natural language into an NLCST-tree.
function ParseWrapper(doc, file) {
  if (!(this instanceof ParseWrapper)) {
    return(new ParseWrapper(doc, file));
  }

  ParseLatin.apply(this, arguments)
  ParseLatin.prototype.position = false;
}

ParseWrapper.prototype.parse = function(value) {
  return(ParseLatin.prototype.parse.call(this, value));
}

ParseWrapper.prototype.tokenize = function(value) {
  return(ParseLatin.prototype.tokenize.call(this, value));
}

module.exports = ParseWrapper;

実は parse-english のコードから継承する部分のみを切り出したものです。 もっとも少しだけ変更も加えてます。 15行目の ParseLatin.prototype.position を false にして 位置情報を記録しないようにしてあります。

以上のコードを組み合わせると…

$ node pos.js
{ type: 'RootNode' }
{ type: 'ParagraphNode' }
{ type: 'SentenceNode' }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'Professor' } ],
  data: { partOfSpeech: 'NNP' } }
{ type: 'WhiteSpaceNode', value: ' ' }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'Bob' } ],
  data: { partOfSpeech: 'NNP' } }
{ type: 'WhiteSpaceNode', value: ' ' }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'Fabry' } ],
  data: { partOfSpeech: 'NN' } }
{ type: 'PunctuationNode', value: ',' }
$ 

ようやく動いてくれました(笑)

品詞情報は 'WordNode' に .data.partOfSpeech として埋め込まれます。 英語の場合はトークン化と品詞情報の設定は個別の処理として扱われますが、 日本語の場合は(ご存知のとおり)形態素解析で両方を一気に解決します。


mecab コマンドの呼び出し

さて、その形態素解析ですが、 まずは安直に MeCab コマンドを呼び出すことにしました。 標準サポートのコマンド同期実行の API を使って、 次のようなインターフェースを作りました。

const execSync = require('child_process').execSync;

//const dic = "-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd";
const fmt = "-F'%m\t%h\n'";
const eos = "-E'\n'";

function single_sync_exec(msg) {
    //var command = 'echo "'+msg+'" | mecab '+dic+' '+fmt+' '+eos;
    var command = 'echo "'+msg+'" | mecab '+fmt+' '+eos;
    const ret = execSync(command);
    var sentences = String(ret).split('\n\n');

    var tokens = sentences[0].split('\n');
    var tkn = [];
    for (var j = 0; j < tokens.length; j++) {
    var each = tokens[j].split('\t');
    var elm = [];
    elm[0] = each[0];
    elm[1] = Number(each[1]);
    tkn.push(elm);
    }
    return(tkn);
}

module.exports = single_sync_exec;

このインターフェースでは mecab コマンドの出力どおり トークンと品詞IDが配列に格納されて返ってきます。 ちなみに、品詞IDについては末尾のIPA品詞体系をご覧ください。 今回は MeCab 標準の IPA 辞書を使いますが、 もし NEologd 辞書が使いたい場合はコメントアウトを外してください。 このインターフェースを実行するとこうなります。

$ cat morphological.js
var MecabCall = require('./mecab_call');

var str = "これこそAT&T ベル研究所のケン・トンプソンとデニス・リッチーの二人がUNIXの論文を初めて発表したシンポジウムである。";

console.log(MecabCall(str));
$ node morphological.js
[ [ 'これ', 59 ],
  [ 'こそ', 16 ],
  [ 'AT&T', 45 ],
  [ 'ベル', 38 ],
  [ '研究所', 38 ],
  [ '', 24 ],
  [ 'ケン', 44 ],
  [ '', 4 ],
  [ 'トンプソン', 43 ],
  [ '', 23 ],
  [ 'デニス', 44 ],
  [ '', 4 ],
  [ 'リッチー', 38 ],
  [ '', 24 ],
  [ '', 48 ],
  [ '', 53 ],
  [ '', 13 ],
  [ 'UNIX', 38 ],
  [ '', 24 ],
  [ '論文', 38 ],
  [ '', 13 ],
  [ '初めて', 34 ],
  [ '発表', 36 ],
  [ '', 31 ],
  [ '', 25 ],
  [ 'シンポジウム', 38 ],
  [ '', 25 ],
  [ 'ある', 25 ],
  [ '', 7 ] ]
$ 

このインターフェースの弱点の1つは 解析対象となるメッセージをコマンドライン経由で MeCab コマンドに引き渡しているところです。 なので、形態素解析の邪魔しないように配慮して、 メッセージを小分けにしてインターフェースをコールする必要があります。 必然的に遅くなりますが「取り敢えず形態素解析ができれば良し」とします。


parse-japanese-mecab の作成

MeCab インターフェースを組み込んだ parse-japanese-mecab を作成しました。
コードを次に示します。

'use strict'

var ParseLatin = require('parse-latin');
var MeCabCall =  require('./mecab_call');
var inspect = require('unist-util-inspect');

// Inherit from `ParseLatin`.
ParseLatinPrototype.prototype = ParseLatin.prototype;

// Constructor to create a `ParseJapanese` prototype.
function ParseLatinPrototype() {
}

ParseJapanese.prototype = new ParseLatinPrototype();

// Transform Japanese natural language into an NLCST-tree.
function ParseJapanese(doc, file) {
  if (!(this instanceof ParseJapanese)) {
    return(new ParseJapanese(doc, file));
  }

  ParseLatin.apply(this, arguments)
  ParseLatin.prototype.position = false;
}

function tokenizeRoot(node) {
  var root = {};
  root.type = 'RootNode';
  root.children = [];
  var para = {};
  para.type = 'ParagraphNode';
  para.children = [];
  var sent = {};
  sent.type = 'SentenceNode';
  sent.children = [];

  for (var i = 0; i < node.length; i++) {
    if (node[i].type == 'PunctuationNode') {
      sent.children.push(node[i]);
      if (node[i].value == '.' || node[i].value == '。') {
    para.children.push(sent);
    sent = {};
    sent.type = 'SentenceNode';
    sent.children = [];
      }
    } else if (node[i].type == 'WhiteSpaceNode') {
      root.children.push(para);
      para = {};
      para.type = 'ParagraphNode';
      para.children = [];
    } else {
      sent.children.push(node[i]);
    }
  }
  root.children.push(para);

  return(root);
}

ParseJapanese.prototype.parse = function(value) {
  //return(ParseLatin.prototype.parse.call(this, value));
  return tokenizeRoot(value);
}

// 'WordNode'
function createWordNode(txt, pos) {
  var text = {};
  text.type  = 'TextNode';
  text.value = txt

  var data = {};
  data.partOfSpeech = pos;

  var word = {};
  word.type = 'WordNode';
  word.children = [];
  word.children.push(text);
  word.data = data;

  return(word);
}

// 'WhiteSpaceNode'
function createWhiteSpaceNode(txt) {
  var word = {};
  word.type  = 'WhiteSpaceNode';
  word.value = txt;
  return(word);
}

// 'PunctuationNode'
function createPunctuationNode(txt) {
  var word = {};
  word.type  = 'PunctuationNode';
  word.value = txt;
  return(word);
}

function tokenize(value) {
  if (value === null || value === undefined) {
    value = '';
  } else if (value instanceof String) {
    value = value.toString();
  }

  if (typeof value !== 'string') {
    // Return the given nodes
    // if this is either an empty array,
    // or an array with a node as a first child.
    if ('length' in value && (!value[0] || value[0].type)) {
      return(value);
    }

    throw new Error("Illegal invocation: '" + value +
            "' is not a valid argument for 'ParseLatin'");
  }

  var tokens = [];

  value = value.replace(/\n/g, "");
  if (value.indexOf('。') <= -1) {
    var ret = MeCabCall(value);
    for (var i = 0; i < ret.length; i++) {
      if (ret[i][1] == 8) {       // 記号  空白
    tokens.push(createWhiteSpaceNode(ret[i][0]));
      } else if (ret[i][1] == 7 ||  // 記号  句点
         ret[i][1] == 9) {  // 記号  読点
    tokens.push(createPunctuationNode(ret[i][0]));
      } else {
    tokens.push(createWordNode(ret[i][0], ret[i][1]));
      }
    }
  } else {
    var sentences = String(value).split('。');
    for (var j = 0; j < sentences.length-1; j++) {
      var ret = MeCabCall(sentences[j]+'。');
      for (var i = 0; i < ret.length; i++) {
    if (ret[i][1] == 8) {     // 記号  空白
      tokens.push(createWhiteSpaceNode(ret[i][0]));
    } else if (ret[i][1] == 7 ||    // 記号  句点
           ret[i][1] == 9) {    // 記号  読点
      tokens.push(createPunctuationNode(ret[i][0]));
    } else {
      tokens.push(createWordNode(ret[i][0], ret[i][1]));
    }
      }
    }
  }
  return(tokens);
}

ParseJapanese.prototype.tokenize = function(value) {
  //return(ParseLatin.prototype.tokenize.call(this, value));
  return tokenize(value);
}

module.exports = ParseJapanese;

パーサーは markdown ドキュメントのパラグラフ毎に呼び出されます。 (mdast 構文木がパラグラフ単位だからでしょう) 複数の文が含まれるパラグラフでは文単位で分割して形態素解析を行います。 また WordNode の間は強制的に WhiteSpaceNode を挿入しています(81行、99行)。 このようにしないとパーサーが誤認識して適切な nlcst 構文木を構成してくれません。


まとめ

ということで、ようやく日本語パーサーが(一応)形になりましたが…

いやぁ parse-latin の解析にはてこずりました(笑)

強いて理由を挙げれば pluggable です。 parse-latin には次のコメントがあります。

All these nodes are pluggable: they come with a use method which accepts a plugin (function(NLCSTNode)). Every time one of these methods are called, the plugin is invoked with the node, allowing for easy modification.

In fact, the internal transformation from tokenize (a list of words, white space, punctuation, and symbols) to tokenizeRoot (an NLCST tree), is also implemented through this mechanism.

これらのノードはすべて pluggable です: プラグインを受け入れる use メソッドが付属しています (function(NLCSTNode))。 これらのメソッドが呼ばれるたびにプラグインがノードとともに呼び出され、簡単に変更できるようになります。

実際、tokenize (単語、空白、句読点、記号のリスト) から tokenizeRoot (NLCSTツリー) への内部変換もこのメカニズムで実装されています。

どうやら構文解釈の機能をプラグインに小分けしているようです。 現時点では、この pluggable のメカニズムを完全に理解した訳ではありません。 なので、WhiteSpaceNode の強制挿入でお茶を濁してます。

ですが…

先を急ぎたいので、まずはこのバージョンで。すいません。

2020/09/13
形態素解析の誤認識を修正する作業をしていたら問題を発見。 ソースを入れ替えました。

以上


付録:IPA品詞体系

こちらから引用しました。

こちらも併用しています。

ID 品詞 細分類1
0 その他 間投 「あ」「ア」のみ
1 フィラー 「えーと」「なんか」など
2 感動詞 「うむ」「お疲れさま」「トホホ」
3 記号 アルファベット 「A-z」
4 記号 一般 「?」「!」「¥」
5 記号 括弧開 「(」「【」など
6 記号 括弧閉 「 )」「】」など
7 記号 句点 「。」「.」のみ
8 記号 空白 「 」のみ
9 記号 読点 「、」「,」のみ
10 形容詞 自立 「美しい」「楽しい」
11 形容詞 接尾 「ったらしい」「っぽい」
12 形容詞 非自立 「づらい」「がたい」「よい」
13 助詞 格助詞 一般 「の」「から」「を」
14 助詞 格助詞 引用 「と」のみ
15 助詞 格助詞 連語 「について」「とかいう」
16 助詞 係助詞 「は」「こそ」「も」「や」
17 助詞 終助詞 「かしら」「ぞ」「っけ」「わい」
18 助詞 接続助詞 「て」「つつ」「および」「ので」
19 助詞 特殊 「かな」「けむ」「にゃ」
20 助詞 副詞化 「と」「に」のみ
21 助詞 副助詞 「くらい」「なんか」「ばっかり」
22 助詞 副助詞/並立助詞/終助詞 「か」のみ
23 助詞 並立助詞 「とか」「だの」「やら」
24 助詞 連体化 「の」のみ
25 助動詞 「ます」「らしい」「です」
26 接続詞 「だから」「しかし」
27 接頭詞 形容詞接続 「お」「まっ」
28 接頭詞 数接続 「計」「毎分」
29 接頭詞 動詞接続 「ぶっ」「引き」
30 接頭詞 名詞接続 「最」「総」
31 動詞 自立 「投げる」
32 動詞 接尾 「しまう」「ちゃう」「願う」
33 動詞 非自立 「しまう」「ちゃう」「願う」
34 副詞 一般 「あいかわらず」「多分」
35 副詞 助詞類接続 「こんなに」「そんなに」
36 名詞 サ変接続 「インプット」「悪化」
37 名詞 ナイ形容詞語幹 「申し訳」「仕方」
38 名詞 一般 「テーブル」
39 名詞 引用文字列 「いわく」のみ
40 名詞 形容動詞語幹 「健康」「安易」「駄目」
41 名詞 固有名詞 一般 北穂高岳」、「電通銀座ビル」、「G1」
42 名詞 固有名詞 人名 一般 グッチ裕三」、「紫式部
43 名詞 固有名詞 人名 「山田」、「ビスコンティ
44 名詞 固有名詞 人名 「B作」、「アントニオ」、「右京太夫
45 名詞 固有名詞 組織 「株式会社◯◯」
46 名詞 固有名詞 地域 一般 「東京」
47 名詞 固有名詞 地域 「日本」
48 名詞 「0」「一」
49 名詞 接続詞的 「◯対◯」「◯兼◯」
50 名詞 接尾 サ変接続 「(可視)化」
51 名詞 接尾 一般 「感」「観」「性」
52 名詞 接尾 形容動詞語幹 「的」「げ」「がち」
53 名詞 接尾 助数詞 「個」「つ」「本」「冊」
54 名詞 接尾 助動詞語幹 「そ」、「そう」のみ
55 名詞 接尾 人名 「君」、「はん」
56 名詞 接尾 地域 「州」、「市内」、「港」
57 名詞 接尾 特殊 「ぶり」、「み」、「方」
58 名詞 接尾 副詞可能 「いっぱい」、「前後」、「次第」
59 名詞 代名詞 一般 「そこ」、「俺」、「こんちくしょう」
60 名詞 代名詞 縮約 「わたしゃ」、「そりゃあ」
61 名詞 動詞非自立的 「しまう」、「ちゃう」、「願う」
62 名詞 特殊 助動詞語幹 「そ」、「そう」のみ
63 名詞 非自立 一般 「こと」、「きらい」、「くせ」、「もの」
64 名詞 非自立 形容動詞語幹 「ふう」、「みたい」のみ
65 名詞 非自立 助動詞語幹 「よ」、「よう」のみ
66 名詞 非自立 副詞可能 「限り」、「さなか」、「うち」
67 名詞 副詞可能 「10月」、「せんだって」、「当分」
68 連体詞 「この」、「いろんな」、「おっきな」、「堂々たる」