Remark(4)mdast-util-to-nlcstとparse-latin

mdast-util-to-nlcst and parse-latin


2020/09/03
藤田昭人


前回 の調査ではmdast構文木からnlcst構文木への複製は mdast-util-to-nlcst で実装されていることがわかりました。 本稿ではmdast-util-to-nlcst と、その下位にある parse-englishparse-dutchparse-latin の関係について調べてみます。

実は日本語文のパーサーというと 「既存の形態素解析機をどのように組み込むか?」 という問題にばかり気を取られていたのですが、調べてみると 「nlcst 構文木をどのように(再)構築するか?」 の方が問題が多いことに気付かされました。 実際、英文と同様のnlcst 構文木が再現できないと、 remark としての使い勝手に影響がありますもんね。 本稿ではそのあたりの解決策を紹介します。


mdast-util-to-nlcst:mdast から nlcst に変換するためのユーティリティ

前回 説明したように mdast-util-to-nlcstremark-retext が提供する mdast 構文木からnlcst 構文木への複製を行う処理の核です。 API は次のエントリーだけ。

toNlcst(tree, file, Parser[, options])

mdast構文木 を対応する仮想ファイルを持つ nlcst に変換します。

パラメータ
tree

位置情報を持つmdast内の構文木MdastNode)。

file

仮想ファイル(VFile

parser

nlcst パーサー(Function)。 例えば、parse-englishparse-dutchparse-latin など。

options.ignore

無視する types のリスト(Array.<string>)。

`table'、'tableRow'、'tableCell' は常に無視されます。

options.source

ソースとしてマークするtypesのリスト(Array.<string>)。

`inlineCode' は常にソースとしてマークされます。

リターン

NlcstNode.

引数 tree として mdast 構文木を、 引数 file には元データ、 そして引数 Parser にパーサーコードを渡すと、 戻り値に nlcst 構文木が返ってきます。

ソースコード

mdast-util-to-nlcst のページには プレーン・テキストを unifiedプラグイン・チェインを 使わないでパースする次のサンプルコードが掲載されています (ちょこっとだけ修正しましたが)。

var toNLCST = require('mdast-util-to-nlcst')
var inspect = require('unist-util-inspect')
var English = require('parse-english')
var remark = require('remark')
var vfile = require('vfile')
var file = vfile('Someone said Hello.')
var tree = remark().parse(file)
console.log("\n# MDAST")
console.log(inspect(tree))
var nlcst = toNLCST(tree, file, English)
console.log("\n# NLCST")
console.log(inspect(nlcst))

このコードを実行すると次のようになります。

$ npm install mdast-util-to-nlcst unist-util-inspect parse-english remark vfile
 ・・・
$ node example.js

# MDAST
root[1] (1:1-1:20, 0-19)
└─0 paragraph[1] (1:1-1:20, 0-19)
    └─0 text "Someone said Hello." (1:1-1:20, 0-19)

# NLCST
RootNode[1] (1:1-1:20, 0-19)
└─0 ParagraphNode[1] (1:1-1:20, 0-19)
    └─0 SentenceNode[6] (1:1-1:20, 0-19)
        ├─0 WordNode[1] (1:1-1:8, 0-7)
        │   └─0 TextNode "Someone" (1:1-1:8, 0-7)
        ├─1 WhiteSpaceNode " " (1:8-1:9, 7-8)
        ├─2 WordNode[1] (1:9-1:13, 8-12)
        │   └─0 TextNode "said" (1:9-1:13, 8-12)
        ├─3 WhiteSpaceNode " " (1:13-1:14, 12-13)
        ├─4 WordNode[1] (1:14-1:19, 13-18)
        │   └─0 TextNode "Hello" (1:14-1:19, 13-18)
        └─5 PunctuationNode "." (1:19-1:20, 18-19)
$ 

Markdown 由来の MDAST とそれを変換した NLCST の違いに注目してください。
MDAST がパラグラフ単位の構文木であるのに対し、 NLCST はワード単位の構文木になっています。
これが Natural Language Concrete Syntax Tree と名付けられている理由なんでしょうね。


parse-latin:nlcst ノードを生成するretextのためのラテン文字言語パーサー

mdast-util-to-nlcstからコールされるパーサーは 引数で明示的に指定してやる必要があります。ドキュメントを見ると parse-englishparse-dutchparse-latin の3種類があるので、当初は何となく「我々は parse-english なのかな?」 などと安易に考えていたのですが…

実は3つとも javascript のクラスとして記述されています。 3者の関係は parse-latin が基底クラスで、 ラテン文字言語向けの汎用パーサーとして実装されています。 残る2つは parse-latin の派生クラスでして、 各々各言語固有の略号に対する例外的な処理以外は parse-latin に依存しているようです。

なので…

nlcst 構文木の(再)構築方法を調べる場合は、 parse-latin に集中して良さそうです。
API は次のとおりです。

ParseLatin#tokenize(value)

valuestring)を、文字と数字(単語)、空白、その他すべて(句読点)にトークン化します。 返されるノードは、段落や文章のないフラットなリストです。

リターン

Array.<Node> — Nodes.

ParseLatin#parse(value)

valuestring)をNLCST構文木トークン化します。 返されるノードは、段落と文を含むRootNodeです。

リターン

Node — Root node.

以上2つが公式APIなんですが…
mdast-util-to-nlcst では、 parse-latin の隠しAPIをもうひとつコールしています。

ParseLatin#tokenizeWhiteSpace(value)

valuestring)で渡される空白、空行("\n\n")などをノード化します。 返されるノードは、WhiteSpaceNodeです。

リターン

Node — WhiteSpace node.

このAPIは主に mdast で検知されたパラグラフ間に WhiteSpaceNode を差し込むためにコールされているように見えます。

どうやら 「ParseLatin#parseトークン化するテキストを渡すとNLCST構文木が返ってくる」 という単機能のパーサーではなさそうです。


parse-wrapper:パーサーの挙動を調べるためのラッパー

…という事で

次のようなパーサーのラッパーを書いて、パーサーの呼び出し順序と 引数、戻り値をチェックしてみることにしました。

まずは呼び出し順序の確認から…

var Latin = require('parse-latin');
var localParser;
var localTokenizeWhiteSpace;

var ParseWrapper = function() {
  console.log("\n### ParseWrapper()");
  localParser = new Latin();
  localTokenizeWhiteSpace = localParser.tokenizeWhiteSpace;
}

ParseWrapper.prototype.parse = function(value) {
  console.log("\n### ParseWrapper.prototype.parse");
  var ret = localParser.parse(value);
  return(ret);
}

ParseWrapper.prototype.tokenize = function(value) {
  console.log("\n### ParseWrapper.prototype.tokenize");
  var ret = localParser.tokenize(value);
  return(ret);
}

ParseWrapper.prototype.tokenizeWhiteSpace = function() {
  console.log("\n### ParseWrapper.prototype.tokenizeWhiteSpace");
  var ret = localTokenizeWhiteSpace.apply(this, arguments);
  return(ret);
}

module.exports = ParseWrapper;

テストプログラムはこんな感じ。

var toNLCST = require('mdast-util-to-nlcst');
var inspect = require('unist-util-inspect');
var English = require('./parse-wrapper');
var remark = require('remark');
var vfile = require('to-vfile');

var file = vfile.readSync('english.md',  'utf8');
var tree = remark().parse(file);
var nlcst = toNLCST(tree, file, English);

これを実行すると次のようになります*1

$ cat english.md
# Twenty Years of Berkeley Unix: Twenty Years of Berkeley Unix From AT&T-Owned to Freely Redistributable

Marshall Kirk McKusick

## Early History

Ken Thompson and Dennis Ritchie presented the first Unix paper at the Symposium on Operating Systems Principles at Purdue University in November 1973. 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.
$ node example.js

### ParseWrapper()

### ParseWrapper.prototype.tokenize

### ParseWrapper.prototype.tokenizeWhiteSpace

### ParseWrapper.prototype.tokenize

### ParseWrapper.prototype.tokenizeWhiteSpace

### ParseWrapper.prototype.tokenize

### ParseWrapper.prototype.tokenizeWhiteSpace

### ParseWrapper.prototype.tokenize

### ParseWrapper.prototype.parse
$

なるほど…

なお、引数と戻り値の確認は長くなりそうなので 末尾 に掲載します。


parse-japanese に向けて

ラッパーを使って、パーサーの呼び出し順序と引数、戻り値をチェックしてみましたが、 整理すると parse-latin の振る舞いは次のようになりそうです。

  1. mdast 構文木を走査してトークン化を行う
    • ParseLatin#tokenize:プレーン・テキストをトークン列に分割
    • ParseLatin#tokenizeWhiteSpaceパラダイム間を検出したら WhiteSpaceNode を挿入
    • いずれもフラットな配列に順次格納していく
  2. 配列に格納したトークンデータをParseLatin#parse を渡しnlcst 構文木を(再)構成
    • RootNodeParagraphNodeSentenceNode を挿入

ここまで調べたところで、ふと閃いたのが 「ParseLatin#tokenizeの部分だけ、日本語の形態素解析に差し替えたら parse-japanese にならない?」 ってことでした。

そこで次回はこのアプローチで parse-japanese を試作してみます。

以上

$ node example.js
root[5] (1:1-15:1, 0-962)
├─0 heading[1] (1:1-1:105, 0-104)
│   │ depth: 1
│   └─0 text "Twenty Years of Berkeley Unix: Twenty Years of Berkeley Unix From AT&T-Owned to Freely Redistributable" (1:3-1:105, 2-104)
├─1 paragraph[1] (5:1-5:23, 108-130)
│   └─0 text "Marshall Kirk McKusick" (5:1-5:23, 108-130)
├─2 heading[1] (9:1-9:17, 134-150)
│   │ depth: 2
│   └─0 text "Early History" (9:4-9:17, 137-150)
├─3 paragraph[1] (11:1-11:335, 152-486)
│   └─0 text "Ken Thompson and Dennis Ritchie presented the first Unix paper at the Symposium on Operating Systems Principles at Purdue University in November 1973. 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." (11:1-11:335, 152-486)
└─4 paragraph[1] (13:1-13:473, 488-960)
    └─0 text "At the time, Berkeley had only large mainframe computer systems doing batch processing, so the first order of business was to get a PDP-11/45 suitable for running with the then-current Version 4 of Unix. The Computer Science Department at Berkeley, together with the Mathematics Department and the Statistics Department, were able to jointly purchase a PDP-11/45. In January 1974, a Version 4 tape was delivered and Unix was installed by graduate student Keith Standiford." (13:1-13:473, 488-960)

### ParseWrapper.prototype.parse

#### in
├─0 WordNode[1] (1:3-1:9, 2-8)
│   └─0 TextNode "Twenty" (1:3-1:9, 2-8)
├─1 WhiteSpaceNode " " (1:9-1:10, 8-9)
├─2 WordNode[1] (1:10-1:15, 9-14)
│   └─0 TextNode "Years" (1:10-1:15, 9-14)
├─3 WhiteSpaceNode " " (1:15-1:16, 14-15)
├─4 WordNode[1] (1:16-1:18, 15-17)
│   └─0 TextNode "of" (1:16-1:18, 15-17)
├─5 WhiteSpaceNode " " (1:18-1:19, 17-18)
├─6 WordNode[1] (1:19-1:27, 18-26)
│   └─0 TextNode "Berkeley" (1:19-1:27, 18-26)
├─7 WhiteSpaceNode " " (1:27-1:28, 26-27)
├─8 WordNode[1] (1:28-1:32, 27-31)
│   └─0 TextNode "Unix" (1:28-1:32, 27-31)
├─9 PunctuationNode ":" (1:32-1:33, 31-32)
├─10 WhiteSpaceNode " " (1:33-1:34, 32-33)
├─11 WordNode[1] (1:34-1:40, 33-39)
│   └─0 TextNode "Twenty" (1:34-1:40, 33-39)
├─12 WhiteSpaceNode " " (1:40-1:41, 39-40)
├─13 WordNode[1] (1:41-1:46, 40-45)
│   └─0 TextNode "Years" (1:41-1:46, 40-45)
├─14 WhiteSpaceNode " " (1:46-1:47, 45-46)
├─15 WordNode[1] (1:47-1:49, 46-48)
│   └─0 TextNode "of" (1:47-1:49, 46-48)
├─16 WhiteSpaceNode " " (1:49-1:50, 48-49)
├─17 WordNode[1] (1:50-1:58, 49-57)
│   └─0 TextNode "Berkeley" (1:50-1:58, 49-57)
├─18 WhiteSpaceNode " " (1:58-1:59, 57-58)
├─19 WordNode[1] (1:59-1:63, 58-62)
│   └─0 TextNode "Unix" (1:59-1:63, 58-62)
├─20 WhiteSpaceNode " " (1:63-1:64, 62-63)
├─21 WordNode[1] (1:64-1:68, 63-67)
│   └─0 TextNode "From" (1:64-1:68, 63-67)
├─22 WhiteSpaceNode " " (1:68-1:69, 67-68)
├─23 WordNode[1] (1:69-1:71, 68-70)
│   └─0 TextNode "AT" (1:69-1:71, 68-70)
├─24 SymbolNode "&" (1:71-1:72, 70-71)
├─25 WordNode[1] (1:72-1:73, 71-72)
│   └─0 TextNode "T" (1:72-1:73, 71-72)
├─26 PunctuationNode "-" (1:73-1:74, 72-73)
├─27 WordNode[1] (1:74-1:79, 73-78)
│   └─0 TextNode "Owned" (1:74-1:79, 73-78)
├─28 WhiteSpaceNode " " (1:79-1:80, 78-79)
├─29 WordNode[1] (1:80-1:82, 79-81)
│   └─0 TextNode "to" (1:80-1:82, 79-81)
├─30 WhiteSpaceNode " " (1:82-1:83, 81-82)
├─31 WordNode[1] (1:83-1:89, 82-88)
│   └─0 TextNode "Freely" (1:83-1:89, 82-88)
├─32 WhiteSpaceNode " " (1:89-1:90, 88-89)
├─33 WordNode[1] (1:90-1:105, 89-104)
│   └─0 TextNode "Redistributable" (1:90-1:105, 89-104)
├─34 WhiteSpaceNode "\n\n\n\n" (1:105-5:1, 104-108)
├─35 WordNode[1] (5:1-5:9, 108-116)
│   └─0 TextNode "Marshall" (5:1-5:9, 108-116)
├─36 WhiteSpaceNode " " (5:9-5:10, 116-117)
├─37 WordNode[1] (5:10-5:14, 117-121)
│   └─0 TextNode "Kirk" (5:10-5:14, 117-121)
├─38 WhiteSpaceNode " " (5:14-5:15, 121-122)
├─39 WordNode[1] (5:15-5:23, 122-130)
│   └─0 TextNode "McKusick" (5:15-5:23, 122-130)
├─40 WhiteSpaceNode "\n\n\n\n" (5:23-9:1, 130-134)
├─41 WordNode[1] (9:4-9:9, 137-142)
│   └─0 TextNode "Early" (9:4-9:9, 137-142)
├─42 WhiteSpaceNode " " (9:9-9:10, 142-143)
├─43 WordNode[1] (9:10-9:17, 143-150)
│   └─0 TextNode "History" (9:10-9:17, 143-150)
├─44 WhiteSpaceNode "\n\n" (9:17-11:1, 150-152)
├─45 WordNode[1] (11:1-11:4, 152-155)
│   └─0 TextNode "Ken" (11:1-11:4, 152-155)
├─46 WhiteSpaceNode " " (11:4-11:5, 155-156)
├─47 WordNode[1] (11:5-11:13, 156-164)
│   └─0 TextNode "Thompson" (11:5-11:13, 156-164)
├─48 WhiteSpaceNode " " (11:13-11:14, 164-165)
├─49 WordNode[1] (11:14-11:17, 165-168)
│   └─0 TextNode "and" (11:14-11:17, 165-168)
├─50 WhiteSpaceNode " " (11:17-11:18, 168-169)
├─51 WordNode[1] (11:18-11:24, 169-175)
│   └─0 TextNode "Dennis" (11:18-11:24, 169-175)
├─52 WhiteSpaceNode " " (11:24-11:25, 175-176)
├─53 WordNode[1] (11:25-11:32, 176-183)
│   └─0 TextNode "Ritchie" (11:25-11:32, 176-183)
├─54 WhiteSpaceNode " " (11:32-11:33, 183-184)
├─55 WordNode[1] (11:33-11:42, 184-193)
│   └─0 TextNode "presented" (11:33-11:42, 184-193)
├─56 WhiteSpaceNode " " (11:42-11:43, 193-194)
├─57 WordNode[1] (11:43-11:46, 194-197)
│   └─0 TextNode "the" (11:43-11:46, 194-197)
├─58 WhiteSpaceNode " " (11:46-11:47, 197-198)
├─59 WordNode[1] (11:47-11:52, 198-203)
│   └─0 TextNode "first" (11:47-11:52, 198-203)
├─60 WhiteSpaceNode " " (11:52-11:53, 203-204)
├─61 WordNode[1] (11:53-11:57, 204-208)
│   └─0 TextNode "Unix" (11:53-11:57, 204-208)
├─62 WhiteSpaceNode " " (11:57-11:58, 208-209)
├─63 WordNode[1] (11:58-11:63, 209-214)
│   └─0 TextNode "paper" (11:58-11:63, 209-214)
├─64 WhiteSpaceNode " " (11:63-11:64, 214-215)
├─65 WordNode[1] (11:64-11:66, 215-217)
│   └─0 TextNode "at" (11:64-11:66, 215-217)
├─66 WhiteSpaceNode " " (11:66-11:67, 217-218)
├─67 WordNode[1] (11:67-11:70, 218-221)
│   └─0 TextNode "the" (11:67-11:70, 218-221)
├─68 WhiteSpaceNode " " (11:70-11:71, 221-222)
├─69 WordNode[1] (11:71-11:80, 222-231)
│   └─0 TextNode "Symposium" (11:71-11:80, 222-231)
├─70 WhiteSpaceNode " " (11:80-11:81, 231-232)
├─71 WordNode[1] (11:81-11:83, 232-234)
│   └─0 TextNode "on" (11:81-11:83, 232-234)
├─72 WhiteSpaceNode " " (11:83-11:84, 234-235)
├─73 WordNode[1] (11:84-11:93, 235-244)
│   └─0 TextNode "Operating" (11:84-11:93, 235-244)
├─74 WhiteSpaceNode " " (11:93-11:94, 244-245)
├─75 WordNode[1] (11:94-11:101, 245-252)
│   └─0 TextNode "Systems" (11:94-11:101, 245-252)
├─76 WhiteSpaceNode " " (11:101-11:102, 252-253)
├─77 WordNode[1] (11:102-11:112, 253-263)
│   └─0 TextNode "Principles" (11:102-11:112, 253-263)
├─78 WhiteSpaceNode " " (11:112-11:113, 263-264)
├─79 WordNode[1] (11:113-11:115, 264-266)
│   └─0 TextNode "at" (11:113-11:115, 264-266)
├─80 WhiteSpaceNode " " (11:115-11:116, 266-267)
├─81 WordNode[1] (11:116-11:122, 267-273)
│   └─0 TextNode "Purdue" (11:116-11:122, 267-273)
├─82 WhiteSpaceNode " " (11:122-11:123, 273-274)
├─83 WordNode[1] (11:123-11:133, 274-284)
│   └─0 TextNode "University" (11:123-11:133, 274-284)
├─84 WhiteSpaceNode " " (11:133-11:134, 284-285)
├─85 WordNode[1] (11:134-11:136, 285-287)
│   └─0 TextNode "in" (11:134-11:136, 285-287)
├─86 WhiteSpaceNode " " (11:136-11:137, 287-288)
├─87 WordNode[1] (11:137-11:145, 288-296)
│   └─0 TextNode "November" (11:137-11:145, 288-296)
├─88 WhiteSpaceNode " " (11:145-11:146, 296-297)
├─89 WordNode[1] (11:146-11:150, 297-301)
│   └─0 TextNode "1973" (11:146-11:150, 297-301)
├─90 PunctuationNode "." (11:150-11:151, 301-302)
├─91 WhiteSpaceNode " " (11:151-11:152, 302-303)
├─92 WordNode[1] (11:152-11:161, 303-312)
│   └─0 TextNode "Professor" (11:152-11:161, 303-312)
├─93 WhiteSpaceNode " " (11:161-11:162, 312-313)
├─94 WordNode[1] (11:162-11:165, 313-316)
│   └─0 TextNode "Bob" (11:162-11:165, 313-316)
├─95 WhiteSpaceNode " " (11:165-11:166, 316-317)
├─96 WordNode[1] (11:166-11:171, 317-322)
│   └─0 TextNode "Fabry" (11:166-11:171, 317-322)
├─97 PunctuationNode "," (11:171-11:172, 322-323)
├─98 WhiteSpaceNode " " (11:172-11:173, 323-324)
├─99 WordNode[1] (11:173-11:175, 324-326)
│   └─0 TextNode "of" (11:173-11:175, 324-326)
├─100 WhiteSpaceNode " " (11:175-11:176, 326-327)
├─101 WordNode[1] (11:176-11:179, 327-330)
│   └─0 TextNode "the" (11:176-11:179, 327-330)
├─102 WhiteSpaceNode " " (11:179-11:180, 330-331)
├─103 WordNode[1] (11:180-11:190, 331-341)
│   └─0 TextNode "University" (11:180-11:190, 331-341)
├─104 WhiteSpaceNode " " (11:190-11:191, 341-342)
├─105 WordNode[1] (11:191-11:193, 342-344)
│   └─0 TextNode "of" (11:191-11:193, 342-344)
├─106 WhiteSpaceNode " " (11:193-11:194, 344-345)
├─107 WordNode[1] (11:194-11:204, 345-355)
│   └─0 TextNode "California" (11:194-11:204, 345-355)
├─108 WhiteSpaceNode " " (11:204-11:205, 355-356)
├─109 WordNode[1] (11:205-11:207, 356-358)
│   └─0 TextNode "at" (11:205-11:207, 356-358)
├─110 WhiteSpaceNode " " (11:207-11:208, 358-359)
├─111 WordNode[1] (11:208-11:216, 359-367)
│   └─0 TextNode "Berkeley" (11:208-11:216, 359-367)
├─112 PunctuationNode "," (11:216-11:217, 367-368)
├─113 WhiteSpaceNode " " (11:217-11:218, 368-369)
├─114 WordNode[1] (11:218-11:221, 369-372)
│   └─0 TextNode "was" (11:218-11:221, 369-372)
├─115 WhiteSpaceNode " " (11:221-11:222, 372-373)
├─116 WordNode[1] (11:222-11:224, 373-375)
│   └─0 TextNode "in" (11:222-11:224, 373-375)
├─117 WhiteSpaceNode " " (11:224-11:225, 375-376)
├─118 WordNode[1] (11:225-11:235, 376-386)
│   └─0 TextNode "attendance" (11:225-11:235, 376-386)
├─119 WhiteSpaceNode " " (11:235-11:236, 386-387)
├─120 WordNode[1] (11:236-11:239, 387-390)
│   └─0 TextNode "and" (11:236-11:239, 387-390)
├─121 WhiteSpaceNode " " (11:239-11:240, 390-391)
├─122 WordNode[1] (11:240-11:251, 391-402)
│   └─0 TextNode "immediately" (11:240-11:251, 391-402)
├─123 WhiteSpaceNode " " (11:251-11:252, 402-403)
├─124 WordNode[1] (11:252-11:258, 403-409)
│   └─0 TextNode "became" (11:252-11:258, 403-409)
├─125 WhiteSpaceNode " " (11:258-11:259, 409-410)
├─126 WordNode[1] (11:259-11:269, 410-420)
│   └─0 TextNode "interested" (11:259-11:269, 410-420)
├─127 WhiteSpaceNode " " (11:269-11:270, 420-421)
├─128 WordNode[1] (11:270-11:272, 421-423)
│   └─0 TextNode "in" (11:270-11:272, 421-423)
├─129 WhiteSpaceNode " " (11:272-11:273, 423-424)
├─130 WordNode[1] (11:273-11:282, 424-433)
│   └─0 TextNode "obtaining" (11:273-11:282, 424-433)
├─131 WhiteSpaceNode " " (11:282-11:283, 433-434)
├─132 WordNode[1] (11:283-11:284, 434-435)
│   └─0 TextNode "a" (11:283-11:284, 434-435)
├─133 WhiteSpaceNode " " (11:284-11:285, 435-436)
├─134 WordNode[1] (11:285-11:289, 436-440)
│   └─0 TextNode "copy" (11:285-11:289, 436-440)
├─135 WhiteSpaceNode " " (11:289-11:290, 440-441)
├─136 WordNode[1] (11:290-11:292, 441-443)
│   └─0 TextNode "of" (11:290-11:292, 441-443)
├─137 WhiteSpaceNode " " (11:292-11:293, 443-444)
├─138 WordNode[1] (11:293-11:296, 444-447)
│   └─0 TextNode "the" (11:293-11:296, 444-447)
├─139 WhiteSpaceNode " " (11:296-11:297, 447-448)
├─140 WordNode[1] (11:297-11:303, 448-454)
│   └─0 TextNode "system" (11:297-11:303, 448-454)
├─141 WhiteSpaceNode " " (11:303-11:304, 454-455)
├─142 WordNode[1] (11:304-11:306, 455-457)
│   └─0 TextNode "to" (11:304-11:306, 455-457)
├─143 WhiteSpaceNode " " (11:306-11:307, 457-458)
├─144 WordNode[1] (11:307-11:317, 458-468)
│   └─0 TextNode "experiment" (11:307-11:317, 458-468)
├─145 WhiteSpaceNode " " (11:317-11:318, 468-469)
├─146 WordNode[1] (11:318-11:322, 469-473)
│   └─0 TextNode "with" (11:318-11:322, 469-473)
├─147 WhiteSpaceNode " " (11:322-11:323, 473-474)
├─148 WordNode[1] (11:323-11:325, 474-476)
│   └─0 TextNode "at" (11:323-11:325, 474-476)
├─149 WhiteSpaceNode " " (11:325-11:326, 476-477)
├─150 WordNode[1] (11:326-11:334, 477-485)
│   └─0 TextNode "Berkeley" (11:326-11:334, 477-485)
├─151 PunctuationNode "." (11:334-11:335, 485-486)
├─152 WhiteSpaceNode "\n\n" (11:335-13:1, 486-488)
├─153 WordNode[1] (13:1-13:3, 488-490)
│   └─0 TextNode "At" (13:1-13:3, 488-490)
├─154 WhiteSpaceNode " " (13:3-13:4, 490-491)
├─155 WordNode[1] (13:4-13:7, 491-494)
│   └─0 TextNode "the" (13:4-13:7, 491-494)
├─156 WhiteSpaceNode " " (13:7-13:8, 494-495)
├─157 WordNode[1] (13:8-13:12, 495-499)
│   └─0 TextNode "time" (13:8-13:12, 495-499)
├─158 PunctuationNode "," (13:12-13:13, 499-500)
├─159 WhiteSpaceNode " " (13:13-13:14, 500-501)
├─160 WordNode[1] (13:14-13:22, 501-509)
│   └─0 TextNode "Berkeley" (13:14-13:22, 501-509)
├─161 WhiteSpaceNode " " (13:22-13:23, 509-510)
├─162 WordNode[1] (13:23-13:26, 510-513)
│   └─0 TextNode "had" (13:23-13:26, 510-513)
├─163 WhiteSpaceNode " " (13:26-13:27, 513-514)
├─164 WordNode[1] (13:27-13:31, 514-518)
│   └─0 TextNode "only" (13:27-13:31, 514-518)
├─165 WhiteSpaceNode " " (13:31-13:32, 518-519)
├─166 WordNode[1] (13:32-13:37, 519-524)
│   └─0 TextNode "large" (13:32-13:37, 519-524)
├─167 WhiteSpaceNode " " (13:37-13:38, 524-525)
├─168 WordNode[1] (13:38-13:47, 525-534)
│   └─0 TextNode "mainframe" (13:38-13:47, 525-534)
├─169 WhiteSpaceNode " " (13:47-13:48, 534-535)
├─170 WordNode[1] (13:48-13:56, 535-543)
│   └─0 TextNode "computer" (13:48-13:56, 535-543)
├─171 WhiteSpaceNode " " (13:56-13:57, 543-544)
├─172 WordNode[1] (13:57-13:64, 544-551)
│   └─0 TextNode "systems" (13:57-13:64, 544-551)
├─173 WhiteSpaceNode " " (13:64-13:65, 551-552)
├─174 WordNode[1] (13:65-13:70, 552-557)
│   └─0 TextNode "doing" (13:65-13:70, 552-557)
├─175 WhiteSpaceNode " " (13:70-13:71, 557-558)
├─176 WordNode[1] (13:71-13:76, 558-563)
│   └─0 TextNode "batch" (13:71-13:76, 558-563)
├─177 WhiteSpaceNode " " (13:76-13:77, 563-564)
├─178 WordNode[1] (13:77-13:87, 564-574)
│   └─0 TextNode "processing" (13:77-13:87, 564-574)
├─179 PunctuationNode "," (13:87-13:88, 574-575)
├─180 WhiteSpaceNode " " (13:88-13:89, 575-576)
├─181 WordNode[1] (13:89-13:91, 576-578)
│   └─0 TextNode "so" (13:89-13:91, 576-578)
├─182 WhiteSpaceNode " " (13:91-13:92, 578-579)
├─183 WordNode[1] (13:92-13:95, 579-582)
│   └─0 TextNode "the" (13:92-13:95, 579-582)
├─184 WhiteSpaceNode " " (13:95-13:96, 582-583)
├─185 WordNode[1] (13:96-13:101, 583-588)
│   └─0 TextNode "first" (13:96-13:101, 583-588)
├─186 WhiteSpaceNode " " (13:101-13:102, 588-589)
├─187 WordNode[1] (13:102-13:107, 589-594)
│   └─0 TextNode "order" (13:102-13:107, 589-594)
├─188 WhiteSpaceNode " " (13:107-13:108, 594-595)
├─189 WordNode[1] (13:108-13:110, 595-597)
│   └─0 TextNode "of" (13:108-13:110, 595-597)
├─190 WhiteSpaceNode " " (13:110-13:111, 597-598)
├─191 WordNode[1] (13:111-13:119, 598-606)
│   └─0 TextNode "business" (13:111-13:119, 598-606)
├─192 WhiteSpaceNode " " (13:119-13:120, 606-607)
├─193 WordNode[1] (13:120-13:123, 607-610)
│   └─0 TextNode "was" (13:120-13:123, 607-610)
├─194 WhiteSpaceNode " " (13:123-13:124, 610-611)
├─195 WordNode[1] (13:124-13:126, 611-613)
│   └─0 TextNode "to" (13:124-13:126, 611-613)
├─196 WhiteSpaceNode " " (13:126-13:127, 613-614)
├─197 WordNode[1] (13:127-13:130, 614-617)
│   └─0 TextNode "get" (13:127-13:130, 614-617)
├─198 WhiteSpaceNode " " (13:130-13:131, 617-618)
├─199 WordNode[1] (13:131-13:132, 618-619)
│   └─0 TextNode "a" (13:131-13:132, 618-619)
├─200 WhiteSpaceNode " " (13:132-13:133, 619-620)
├─201 WordNode[1] (13:133-13:136, 620-623)
│   └─0 TextNode "PDP" (13:133-13:136, 620-623)
├─202 PunctuationNode "-" (13:136-13:137, 623-624)
├─203 WordNode[1] (13:137-13:139, 624-626)
│   └─0 TextNode "11" (13:137-13:139, 624-626)
├─204 PunctuationNode "/" (13:139-13:140, 626-627)
├─205 WordNode[1] (13:140-13:142, 627-629)
│   └─0 TextNode "45" (13:140-13:142, 627-629)
├─206 WhiteSpaceNode " " (13:142-13:143, 629-630)
├─207 WordNode[1] (13:143-13:151, 630-638)
│   └─0 TextNode "suitable" (13:143-13:151, 630-638)
├─208 WhiteSpaceNode " " (13:151-13:152, 638-639)
├─209 WordNode[1] (13:152-13:155, 639-642)
│   └─0 TextNode "for" (13:152-13:155, 639-642)
├─210 WhiteSpaceNode " " (13:155-13:156, 642-643)
├─211 WordNode[1] (13:156-13:163, 643-650)
│   └─0 TextNode "running" (13:156-13:163, 643-650)
├─212 WhiteSpaceNode " " (13:163-13:164, 650-651)
├─213 WordNode[1] (13:164-13:168, 651-655)
│   └─0 TextNode "with" (13:164-13:168, 651-655)
├─214 WhiteSpaceNode " " (13:168-13:169, 655-656)
├─215 WordNode[1] (13:169-13:172, 656-659)
│   └─0 TextNode "the" (13:169-13:172, 656-659)
├─216 WhiteSpaceNode " " (13:172-13:173, 659-660)
├─217 WordNode[1] (13:173-13:177, 660-664)
│   └─0 TextNode "then" (13:173-13:177, 660-664)
├─218 PunctuationNode "-" (13:177-13:178, 664-665)
├─219 WordNode[1] (13:178-13:185, 665-672)
│   └─0 TextNode "current" (13:178-13:185, 665-672)
├─220 WhiteSpaceNode " " (13:185-13:186, 672-673)
├─221 WordNode[1] (13:186-13:193, 673-680)
│   └─0 TextNode "Version" (13:186-13:193, 673-680)
├─222 WhiteSpaceNode " " (13:193-13:194, 680-681)
├─223 WordNode[1] (13:194-13:195, 681-682)
│   └─0 TextNode "4" (13:194-13:195, 681-682)
├─224 WhiteSpaceNode " " (13:195-13:196, 682-683)
├─225 WordNode[1] (13:196-13:198, 683-685)
│   └─0 TextNode "of" (13:196-13:198, 683-685)
├─226 WhiteSpaceNode " " (13:198-13:199, 685-686)
├─227 WordNode[1] (13:199-13:203, 686-690)
│   └─0 TextNode "Unix" (13:199-13:203, 686-690)
├─228 PunctuationNode "." (13:203-13:204, 690-691)
├─229 WhiteSpaceNode " " (13:204-13:205, 691-692)
├─230 WordNode[1] (13:205-13:208, 692-695)
│   └─0 TextNode "The" (13:205-13:208, 692-695)
├─231 WhiteSpaceNode " " (13:208-13:209, 695-696)
├─232 WordNode[1] (13:209-13:217, 696-704)
│   └─0 TextNode "Computer" (13:209-13:217, 696-704)
├─233 WhiteSpaceNode " " (13:217-13:218, 704-705)
├─234 WordNode[1] (13:218-13:225, 705-712)
│   └─0 TextNode "Science" (13:218-13:225, 705-712)
├─235 WhiteSpaceNode " " (13:225-13:226, 712-713)
├─236 WordNode[1] (13:226-13:236, 713-723)
│   └─0 TextNode "Department" (13:226-13:236, 713-723)
├─237 WhiteSpaceNode " " (13:236-13:237, 723-724)
├─238 WordNode[1] (13:237-13:239, 724-726)
│   └─0 TextNode "at" (13:237-13:239, 724-726)
├─239 WhiteSpaceNode " " (13:239-13:240, 726-727)
├─240 WordNode[1] (13:240-13:248, 727-735)
│   └─0 TextNode "Berkeley" (13:240-13:248, 727-735)
├─241 PunctuationNode "," (13:248-13:249, 735-736)
├─242 WhiteSpaceNode " " (13:249-13:250, 736-737)
├─243 WordNode[1] (13:250-13:258, 737-745)
│   └─0 TextNode "together" (13:250-13:258, 737-745)
├─244 WhiteSpaceNode " " (13:258-13:259, 745-746)
├─245 WordNode[1] (13:259-13:263, 746-750)
│   └─0 TextNode "with" (13:259-13:263, 746-750)
├─246 WhiteSpaceNode " " (13:263-13:264, 750-751)
├─247 WordNode[1] (13:264-13:267, 751-754)
│   └─0 TextNode "the" (13:264-13:267, 751-754)
├─248 WhiteSpaceNode " " (13:267-13:268, 754-755)
├─249 WordNode[1] (13:268-13:279, 755-766)
│   └─0 TextNode "Mathematics" (13:268-13:279, 755-766)
├─250 WhiteSpaceNode " " (13:279-13:280, 766-767)
├─251 WordNode[1] (13:280-13:290, 767-777)
│   └─0 TextNode "Department" (13:280-13:290, 767-777)
├─252 WhiteSpaceNode " " (13:290-13:291, 777-778)
├─253 WordNode[1] (13:291-13:294, 778-781)
│   └─0 TextNode "and" (13:291-13:294, 778-781)
├─254 WhiteSpaceNode " " (13:294-13:295, 781-782)
├─255 WordNode[1] (13:295-13:298, 782-785)
│   └─0 TextNode "the" (13:295-13:298, 782-785)
├─256 WhiteSpaceNode " " (13:298-13:299, 785-786)
├─257 WordNode[1] (13:299-13:309, 786-796)
│   └─0 TextNode "Statistics" (13:299-13:309, 786-796)
├─258 WhiteSpaceNode " " (13:309-13:310, 796-797)
├─259 WordNode[1] (13:310-13:320, 797-807)
│   └─0 TextNode "Department" (13:310-13:320, 797-807)
├─260 PunctuationNode "," (13:320-13:321, 807-808)
├─261 WhiteSpaceNode " " (13:321-13:322, 808-809)
├─262 WordNode[1] (13:322-13:326, 809-813)
│   └─0 TextNode "were" (13:322-13:326, 809-813)
├─263 WhiteSpaceNode " " (13:326-13:327, 813-814)
├─264 WordNode[1] (13:327-13:331, 814-818)
│   └─0 TextNode "able" (13:327-13:331, 814-818)
├─265 WhiteSpaceNode " " (13:331-13:332, 818-819)
├─266 WordNode[1] (13:332-13:334, 819-821)
│   └─0 TextNode "to" (13:332-13:334, 819-821)
├─267 WhiteSpaceNode " " (13:334-13:335, 821-822)
├─268 WordNode[1] (13:335-13:342, 822-829)
│   └─0 TextNode "jointly" (13:335-13:342, 822-829)
├─269 WhiteSpaceNode " " (13:342-13:343, 829-830)
├─270 WordNode[1] (13:343-13:351, 830-838)
│   └─0 TextNode "purchase" (13:343-13:351, 830-838)
├─271 WhiteSpaceNode " " (13:351-13:352, 838-839)
├─272 WordNode[1] (13:352-13:353, 839-840)
│   └─0 TextNode "a" (13:352-13:353, 839-840)
├─273 WhiteSpaceNode " " (13:353-13:354, 840-841)
├─274 WordNode[1] (13:354-13:357, 841-844)
│   └─0 TextNode "PDP" (13:354-13:357, 841-844)
├─275 PunctuationNode "-" (13:357-13:358, 844-845)
├─276 WordNode[1] (13:358-13:360, 845-847)
│   └─0 TextNode "11" (13:358-13:360, 845-847)
├─277 PunctuationNode "/" (13:360-13:361, 847-848)
├─278 WordNode[1] (13:361-13:363, 848-850)
│   └─0 TextNode "45" (13:361-13:363, 848-850)
├─279 PunctuationNode "." (13:363-13:364, 850-851)
├─280 WhiteSpaceNode " " (13:364-13:365, 851-852)
├─281 WordNode[1] (13:365-13:367, 852-854)
│   └─0 TextNode "In" (13:365-13:367, 852-854)
├─282 WhiteSpaceNode " " (13:367-13:368, 854-855)
├─283 WordNode[1] (13:368-13:375, 855-862)
│   └─0 TextNode "January" (13:368-13:375, 855-862)
├─284 WhiteSpaceNode " " (13:375-13:376, 862-863)
├─285 WordNode[1] (13:376-13:380, 863-867)
│   └─0 TextNode "1974" (13:376-13:380, 863-867)
├─286 PunctuationNode "," (13:380-13:381, 867-868)
├─287 WhiteSpaceNode " " (13:381-13:382, 868-869)
├─288 WordNode[1] (13:382-13:383, 869-870)
│   └─0 TextNode "a" (13:382-13:383, 869-870)
├─289 WhiteSpaceNode " " (13:383-13:384, 870-871)
├─290 WordNode[1] (13:384-13:391, 871-878)
│   └─0 TextNode "Version" (13:384-13:391, 871-878)
├─291 WhiteSpaceNode " " (13:391-13:392, 878-879)
├─292 WordNode[1] (13:392-13:393, 879-880)
│   └─0 TextNode "4" (13:392-13:393, 879-880)
├─293 WhiteSpaceNode " " (13:393-13:394, 880-881)
├─294 WordNode[1] (13:394-13:398, 881-885)
│   └─0 TextNode "tape" (13:394-13:398, 881-885)
├─295 WhiteSpaceNode " " (13:398-13:399, 885-886)
├─296 WordNode[1] (13:399-13:402, 886-889)
│   └─0 TextNode "was" (13:399-13:402, 886-889)
├─297 WhiteSpaceNode " " (13:402-13:403, 889-890)
├─298 WordNode[1] (13:403-13:412, 890-899)
│   └─0 TextNode "delivered" (13:403-13:412, 890-899)
├─299 WhiteSpaceNode " " (13:412-13:413, 899-900)
├─300 WordNode[1] (13:413-13:416, 900-903)
│   └─0 TextNode "and" (13:413-13:416, 900-903)
├─301 WhiteSpaceNode " " (13:416-13:417, 903-904)
├─302 WordNode[1] (13:417-13:421, 904-908)
│   └─0 TextNode "Unix" (13:417-13:421, 904-908)
├─303 WhiteSpaceNode " " (13:421-13:422, 908-909)
├─304 WordNode[1] (13:422-13:425, 909-912)
│   └─0 TextNode "was" (13:422-13:425, 909-912)
├─305 WhiteSpaceNode " " (13:425-13:426, 912-913)
├─306 WordNode[1] (13:426-13:435, 913-922)
│   └─0 TextNode "installed" (13:426-13:435, 913-922)
├─307 WhiteSpaceNode " " (13:435-13:436, 922-923)
├─308 WordNode[1] (13:436-13:438, 923-925)
│   └─0 TextNode "by" (13:436-13:438, 923-925)
├─309 WhiteSpaceNode " " (13:438-13:439, 925-926)
├─310 WordNode[1] (13:439-13:447, 926-934)
│   └─0 TextNode "graduate" (13:439-13:447, 926-934)
├─311 WhiteSpaceNode " " (13:447-13:448, 934-935)
├─312 WordNode[1] (13:448-13:455, 935-942)
│   └─0 TextNode "student" (13:448-13:455, 935-942)
├─313 WhiteSpaceNode " " (13:455-13:456, 942-943)
├─314 WordNode[1] (13:456-13:461, 943-948)
│   └─0 TextNode "Keith" (13:456-13:461, 943-948)
├─315 WhiteSpaceNode " " (13:461-13:462, 948-949)
├─316 WordNode[1] (13:462-13:472, 949-959)
│   └─0 TextNode "Standiford" (13:462-13:472, 949-959)
└─317 PunctuationNode "." (13:472-13:473, 959-960)

#### out
RootNode[9] (1:3-13:473, 2-960)
├─0 ParagraphNode[1] (1:3-1:105, 2-104)
│   └─0 SentenceNode[30] (1:3-1:105, 2-104)
│       ├─0 WordNode[1] (1:3-1:9, 2-8)
│       │   └─0 TextNode "Twenty" (1:3-1:9, 2-8)
│       ├─1 WhiteSpaceNode " " (1:9-1:10, 8-9)
│       ├─2 WordNode[1] (1:10-1:15, 9-14)
│       │   └─0 TextNode "Years" (1:10-1:15, 9-14)
│       ├─3 WhiteSpaceNode " " (1:15-1:16, 14-15)
│       ├─4 WordNode[1] (1:16-1:18, 15-17)
│       │   └─0 TextNode "of" (1:16-1:18, 15-17)
│       ├─5 WhiteSpaceNode " " (1:18-1:19, 17-18)
│       ├─6 WordNode[1] (1:19-1:27, 18-26)
│       │   └─0 TextNode "Berkeley" (1:19-1:27, 18-26)
│       ├─7 WhiteSpaceNode " " (1:27-1:28, 26-27)
│       ├─8 WordNode[1] (1:28-1:32, 27-31)
│       │   └─0 TextNode "Unix" (1:28-1:32, 27-31)
│       ├─9 PunctuationNode ":" (1:32-1:33, 31-32)
│       ├─10 WhiteSpaceNode " " (1:33-1:34, 32-33)
│       ├─11 WordNode[1] (1:34-1:40, 33-39)
│       │   └─0 TextNode "Twenty" (1:34-1:40, 33-39)
│       ├─12 WhiteSpaceNode " " (1:40-1:41, 39-40)
│       ├─13 WordNode[1] (1:41-1:46, 40-45)
│       │   └─0 TextNode "Years" (1:41-1:46, 40-45)
│       ├─14 WhiteSpaceNode " " (1:46-1:47, 45-46)
│       ├─15 WordNode[1] (1:47-1:49, 46-48)
│       │   └─0 TextNode "of" (1:47-1:49, 46-48)
│       ├─16 WhiteSpaceNode " " (1:49-1:50, 48-49)
│       ├─17 WordNode[1] (1:50-1:58, 49-57)
│       │   └─0 TextNode "Berkeley" (1:50-1:58, 49-57)
│       ├─18 WhiteSpaceNode " " (1:58-1:59, 57-58)
│       ├─19 WordNode[1] (1:59-1:63, 58-62)
│       │   └─0 TextNode "Unix" (1:59-1:63, 58-62)
│       ├─20 WhiteSpaceNode " " (1:63-1:64, 62-63)
│       ├─21 WordNode[1] (1:64-1:68, 63-67)
│       │   └─0 TextNode "From" (1:64-1:68, 63-67)
│       ├─22 WhiteSpaceNode " " (1:68-1:69, 67-68)
│       ├─23 WordNode[5] (1:69-1:79, 68-78)
│       │   ├─0 TextNode "AT" (1:69-1:71, 68-70)
│       │   ├─1 SymbolNode "&" (1:71-1:72, 70-71)
│       │   ├─2 TextNode "T" (1:72-1:73, 71-72)
│       │   ├─3 PunctuationNode "-" (1:73-1:74, 72-73)
│       │   └─4 TextNode "Owned" (1:74-1:79, 73-78)
│       ├─24 WhiteSpaceNode " " (1:79-1:80, 78-79)
│       ├─25 WordNode[1] (1:80-1:82, 79-81)
│       │   └─0 TextNode "to" (1:80-1:82, 79-81)
│       ├─26 WhiteSpaceNode " " (1:82-1:83, 81-82)
│       ├─27 WordNode[1] (1:83-1:89, 82-88)
│       │   └─0 TextNode "Freely" (1:83-1:89, 82-88)
│       ├─28 WhiteSpaceNode " " (1:89-1:90, 88-89)
│       └─29 WordNode[1] (1:90-1:105, 89-104)
│           └─0 TextNode "Redistributable" (1:90-1:105, 89-104)
├─1 WhiteSpaceNode "\n\n\n\n" (1:105-5:1, 104-108)
├─2 ParagraphNode[1] (5:1-5:23, 108-130)
│   └─0 SentenceNode[5] (5:1-5:23, 108-130)
│       ├─0 WordNode[1] (5:1-5:9, 108-116)
│       │   └─0 TextNode "Marshall" (5:1-5:9, 108-116)
│       ├─1 WhiteSpaceNode " " (5:9-5:10, 116-117)
│       ├─2 WordNode[1] (5:10-5:14, 117-121)
│       │   └─0 TextNode "Kirk" (5:10-5:14, 117-121)
│       ├─3 WhiteSpaceNode " " (5:14-5:15, 121-122)
│       └─4 WordNode[1] (5:15-5:23, 122-130)
│           └─0 TextNode "McKusick" (5:15-5:23, 122-130)
├─3 WhiteSpaceNode "\n\n\n\n" (5:23-9:1, 130-134)
├─4 ParagraphNode[1] (9:4-9:17, 137-150)
│   └─0 SentenceNode[3] (9:4-9:17, 137-150)
│       ├─0 WordNode[1] (9:4-9:9, 137-142)
│       │   └─0 TextNode "Early" (9:4-9:9, 137-142)
│       ├─1 WhiteSpaceNode " " (9:9-9:10, 142-143)
│       └─2 WordNode[1] (9:10-9:17, 143-150)
│           └─0 TextNode "History" (9:10-9:17, 143-150)
├─5 WhiteSpaceNode "\n\n" (9:17-11:1, 150-152)
├─6 ParagraphNode[3] (11:1-11:335, 152-486)
│   ├─0 SentenceNode[46] (11:1-11:151, 152-302)
│   │   ├─0 WordNode[1] (11:1-11:4, 152-155)
│   │   │   └─0 TextNode "Ken" (11:1-11:4, 152-155)
│   │   ├─1 WhiteSpaceNode " " (11:4-11:5, 155-156)
│   │   ├─2 WordNode[1] (11:5-11:13, 156-164)
│   │   │   └─0 TextNode "Thompson" (11:5-11:13, 156-164)
│   │   ├─3 WhiteSpaceNode " " (11:13-11:14, 164-165)
│   │   ├─4 WordNode[1] (11:14-11:17, 165-168)
│   │   │   └─0 TextNode "and" (11:14-11:17, 165-168)
│   │   ├─5 WhiteSpaceNode " " (11:17-11:18, 168-169)
│   │   ├─6 WordNode[1] (11:18-11:24, 169-175)
│   │   │   └─0 TextNode "Dennis" (11:18-11:24, 169-175)
│   │   ├─7 WhiteSpaceNode " " (11:24-11:25, 175-176)
│   │   ├─8 WordNode[1] (11:25-11:32, 176-183)
│   │   │   └─0 TextNode "Ritchie" (11:25-11:32, 176-183)
│   │   ├─9 WhiteSpaceNode " " (11:32-11:33, 183-184)
│   │   ├─10 WordNode[1] (11:33-11:42, 184-193)
│   │   │   └─0 TextNode "presented" (11:33-11:42, 184-193)
│   │   ├─11 WhiteSpaceNode " " (11:42-11:43, 193-194)
│   │   ├─12 WordNode[1] (11:43-11:46, 194-197)
│   │   │   └─0 TextNode "the" (11:43-11:46, 194-197)
│   │   ├─13 WhiteSpaceNode " " (11:46-11:47, 197-198)
│   │   ├─14 WordNode[1] (11:47-11:52, 198-203)
│   │   │   └─0 TextNode "first" (11:47-11:52, 198-203)
│   │   ├─15 WhiteSpaceNode " " (11:52-11:53, 203-204)
│   │   ├─16 WordNode[1] (11:53-11:57, 204-208)
│   │   │   └─0 TextNode "Unix" (11:53-11:57, 204-208)
│   │   ├─17 WhiteSpaceNode " " (11:57-11:58, 208-209)
│   │   ├─18 WordNode[1] (11:58-11:63, 209-214)
│   │   │   └─0 TextNode "paper" (11:58-11:63, 209-214)
│   │   ├─19 WhiteSpaceNode " " (11:63-11:64, 214-215)
│   │   ├─20 WordNode[1] (11:64-11:66, 215-217)
│   │   │   └─0 TextNode "at" (11:64-11:66, 215-217)
│   │   ├─21 WhiteSpaceNode " " (11:66-11:67, 217-218)
│   │   ├─22 WordNode[1] (11:67-11:70, 218-221)
│   │   │   └─0 TextNode "the" (11:67-11:70, 218-221)
│   │   ├─23 WhiteSpaceNode " " (11:70-11:71, 221-222)
│   │   ├─24 WordNode[1] (11:71-11:80, 222-231)
│   │   │   └─0 TextNode "Symposium" (11:71-11:80, 222-231)
│   │   ├─25 WhiteSpaceNode " " (11:80-11:81, 231-232)
│   │   ├─26 WordNode[1] (11:81-11:83, 232-234)
│   │   │   └─0 TextNode "on" (11:81-11:83, 232-234)
│   │   ├─27 WhiteSpaceNode " " (11:83-11:84, 234-235)
│   │   ├─28 WordNode[1] (11:84-11:93, 235-244)
│   │   │   └─0 TextNode "Operating" (11:84-11:93, 235-244)
│   │   ├─29 WhiteSpaceNode " " (11:93-11:94, 244-245)
│   │   ├─30 WordNode[1] (11:94-11:101, 245-252)
│   │   │   └─0 TextNode "Systems" (11:94-11:101, 245-252)
│   │   ├─31 WhiteSpaceNode " " (11:101-11:102, 252-253)
│   │   ├─32 WordNode[1] (11:102-11:112, 253-263)
│   │   │   └─0 TextNode "Principles" (11:102-11:112, 253-263)
│   │   ├─33 WhiteSpaceNode " " (11:112-11:113, 263-264)
│   │   ├─34 WordNode[1] (11:113-11:115, 264-266)
│   │   │   └─0 TextNode "at" (11:113-11:115, 264-266)
│   │   ├─35 WhiteSpaceNode " " (11:115-11:116, 266-267)
│   │   ├─36 WordNode[1] (11:116-11:122, 267-273)
│   │   │   └─0 TextNode "Purdue" (11:116-11:122, 267-273)
│   │   ├─37 WhiteSpaceNode " " (11:122-11:123, 273-274)
│   │   ├─38 WordNode[1] (11:123-11:133, 274-284)
│   │   │   └─0 TextNode "University" (11:123-11:133, 274-284)
│   │   ├─39 WhiteSpaceNode " " (11:133-11:134, 284-285)
│   │   ├─40 WordNode[1] (11:134-11:136, 285-287)
│   │   │   └─0 TextNode "in" (11:134-11:136, 285-287)
│   │   ├─41 WhiteSpaceNode " " (11:136-11:137, 287-288)
│   │   ├─42 WordNode[1] (11:137-11:145, 288-296)
│   │   │   └─0 TextNode "November" (11:137-11:145, 288-296)
│   │   ├─43 WhiteSpaceNode " " (11:145-11:146, 296-297)
│   │   ├─44 WordNode[1] (11:146-11:150, 297-301)
│   │   │   └─0 TextNode "1973" (11:146-11:150, 297-301)
│   │   └─45 PunctuationNode "." (11:150-11:151, 301-302)
│   ├─1 WhiteSpaceNode " " (11:151-11:152, 302-303)
│   └─2 SentenceNode[60] (11:152-11:335, 303-486)
│       ├─0 WordNode[1] (11:152-11:161, 303-312)
│       │   └─0 TextNode "Professor" (11:152-11:161, 303-312)
│       ├─1 WhiteSpaceNode " " (11:161-11:162, 312-313)
│       ├─2 WordNode[1] (11:162-11:165, 313-316)
│       │   └─0 TextNode "Bob" (11:162-11:165, 313-316)
│       ├─3 WhiteSpaceNode " " (11:165-11:166, 316-317)
│       ├─4 WordNode[1] (11:166-11:171, 317-322)
│       │   └─0 TextNode "Fabry" (11:166-11:171, 317-322)
│       ├─5 PunctuationNode "," (11:171-11:172, 322-323)
│       ├─6 WhiteSpaceNode " " (11:172-11:173, 323-324)
│       ├─7 WordNode[1] (11:173-11:175, 324-326)
│       │   └─0 TextNode "of" (11:173-11:175, 324-326)
│       ├─8 WhiteSpaceNode " " (11:175-11:176, 326-327)
│       ├─9 WordNode[1] (11:176-11:179, 327-330)
│       │   └─0 TextNode "the" (11:176-11:179, 327-330)
│       ├─10 WhiteSpaceNode " " (11:179-11:180, 330-331)
│       ├─11 WordNode[1] (11:180-11:190, 331-341)
│       │   └─0 TextNode "University" (11:180-11:190, 331-341)
│       ├─12 WhiteSpaceNode " " (11:190-11:191, 341-342)
│       ├─13 WordNode[1] (11:191-11:193, 342-344)
│       │   └─0 TextNode "of" (11:191-11:193, 342-344)
│       ├─14 WhiteSpaceNode " " (11:193-11:194, 344-345)
│       ├─15 WordNode[1] (11:194-11:204, 345-355)
│       │   └─0 TextNode "California" (11:194-11:204, 345-355)
│       ├─16 WhiteSpaceNode " " (11:204-11:205, 355-356)
│       ├─17 WordNode[1] (11:205-11:207, 356-358)
│       │   └─0 TextNode "at" (11:205-11:207, 356-358)
│       ├─18 WhiteSpaceNode " " (11:207-11:208, 358-359)
│       ├─19 WordNode[1] (11:208-11:216, 359-367)
│       │   └─0 TextNode "Berkeley" (11:208-11:216, 359-367)
│       ├─20 PunctuationNode "," (11:216-11:217, 367-368)
│       ├─21 WhiteSpaceNode " " (11:217-11:218, 368-369)
│       ├─22 WordNode[1] (11:218-11:221, 369-372)
│       │   └─0 TextNode "was" (11:218-11:221, 369-372)
│       ├─23 WhiteSpaceNode " " (11:221-11:222, 372-373)
│       ├─24 WordNode[1] (11:222-11:224, 373-375)
│       │   └─0 TextNode "in" (11:222-11:224, 373-375)
│       ├─25 WhiteSpaceNode " " (11:224-11:225, 375-376)
│       ├─26 WordNode[1] (11:225-11:235, 376-386)
│       │   └─0 TextNode "attendance" (11:225-11:235, 376-386)
│       ├─27 WhiteSpaceNode " " (11:235-11:236, 386-387)
│       ├─28 WordNode[1] (11:236-11:239, 387-390)
│       │   └─0 TextNode "and" (11:236-11:239, 387-390)
│       ├─29 WhiteSpaceNode " " (11:239-11:240, 390-391)
│       ├─30 WordNode[1] (11:240-11:251, 391-402)
│       │   └─0 TextNode "immediately" (11:240-11:251, 391-402)
│       ├─31 WhiteSpaceNode " " (11:251-11:252, 402-403)
│       ├─32 WordNode[1] (11:252-11:258, 403-409)
│       │   └─0 TextNode "became" (11:252-11:258, 403-409)
│       ├─33 WhiteSpaceNode " " (11:258-11:259, 409-410)
│       ├─34 WordNode[1] (11:259-11:269, 410-420)
│       │   └─0 TextNode "interested" (11:259-11:269, 410-420)
│       ├─35 WhiteSpaceNode " " (11:269-11:270, 420-421)
│       ├─36 WordNode[1] (11:270-11:272, 421-423)
│       │   └─0 TextNode "in" (11:270-11:272, 421-423)
│       ├─37 WhiteSpaceNode " " (11:272-11:273, 423-424)
│       ├─38 WordNode[1] (11:273-11:282, 424-433)
│       │   └─0 TextNode "obtaining" (11:273-11:282, 424-433)
│       ├─39 WhiteSpaceNode " " (11:282-11:283, 433-434)
│       ├─40 WordNode[1] (11:283-11:284, 434-435)
│       │   └─0 TextNode "a" (11:283-11:284, 434-435)
│       ├─41 WhiteSpaceNode " " (11:284-11:285, 435-436)
│       ├─42 WordNode[1] (11:285-11:289, 436-440)
│       │   └─0 TextNode "copy" (11:285-11:289, 436-440)
│       ├─43 WhiteSpaceNode " " (11:289-11:290, 440-441)
│       ├─44 WordNode[1] (11:290-11:292, 441-443)
│       │   └─0 TextNode "of" (11:290-11:292, 441-443)
│       ├─45 WhiteSpaceNode " " (11:292-11:293, 443-444)
│       ├─46 WordNode[1] (11:293-11:296, 444-447)
│       │   └─0 TextNode "the" (11:293-11:296, 444-447)
│       ├─47 WhiteSpaceNode " " (11:296-11:297, 447-448)
│       ├─48 WordNode[1] (11:297-11:303, 448-454)
│       │   └─0 TextNode "system" (11:297-11:303, 448-454)
│       ├─49 WhiteSpaceNode " " (11:303-11:304, 454-455)
│       ├─50 WordNode[1] (11:304-11:306, 455-457)
│       │   └─0 TextNode "to" (11:304-11:306, 455-457)
│       ├─51 WhiteSpaceNode " " (11:306-11:307, 457-458)
│       ├─52 WordNode[1] (11:307-11:317, 458-468)
│       │   └─0 TextNode "experiment" (11:307-11:317, 458-468)
│       ├─53 WhiteSpaceNode " " (11:317-11:318, 468-469)
│       ├─54 WordNode[1] (11:318-11:322, 469-473)
│       │   └─0 TextNode "with" (11:318-11:322, 469-473)
│       ├─55 WhiteSpaceNode " " (11:322-11:323, 473-474)
│       ├─56 WordNode[1] (11:323-11:325, 474-476)
│       │   └─0 TextNode "at" (11:323-11:325, 474-476)
│       ├─57 WhiteSpaceNode " " (11:325-11:326, 476-477)
│       ├─58 WordNode[1] (11:326-11:334, 477-485)
│       │   └─0 TextNode "Berkeley" (11:326-11:334, 477-485)
│       └─59 PunctuationNode "." (11:334-11:335, 485-486)
├─7 WhiteSpaceNode "\n\n" (11:335-13:1, 486-488)
└─8 ParagraphNode[3] (13:1-13:473, 488-960)
    ├─0 SentenceNode[72] (13:1-13:204, 488-691)
    │   ├─0 WordNode[1] (13:1-13:3, 488-490)
    │   │   └─0 TextNode "At" (13:1-13:3, 488-490)
    │   ├─1 WhiteSpaceNode " " (13:3-13:4, 490-491)
    │   ├─2 WordNode[1] (13:4-13:7, 491-494)
    │   │   └─0 TextNode "the" (13:4-13:7, 491-494)
    │   ├─3 WhiteSpaceNode " " (13:7-13:8, 494-495)
    │   ├─4 WordNode[1] (13:8-13:12, 495-499)
    │   │   └─0 TextNode "time" (13:8-13:12, 495-499)
    │   ├─5 PunctuationNode "," (13:12-13:13, 499-500)
    │   ├─6 WhiteSpaceNode " " (13:13-13:14, 500-501)
    │   ├─7 WordNode[1] (13:14-13:22, 501-509)
    │   │   └─0 TextNode "Berkeley" (13:14-13:22, 501-509)
    │   ├─8 WhiteSpaceNode " " (13:22-13:23, 509-510)
    │   ├─9 WordNode[1] (13:23-13:26, 510-513)
    │   │   └─0 TextNode "had" (13:23-13:26, 510-513)
    │   ├─10 WhiteSpaceNode " " (13:26-13:27, 513-514)
    │   ├─11 WordNode[1] (13:27-13:31, 514-518)
    │   │   └─0 TextNode "only" (13:27-13:31, 514-518)
    │   ├─12 WhiteSpaceNode " " (13:31-13:32, 518-519)
    │   ├─13 WordNode[1] (13:32-13:37, 519-524)
    │   │   └─0 TextNode "large" (13:32-13:37, 519-524)
    │   ├─14 WhiteSpaceNode " " (13:37-13:38, 524-525)
    │   ├─15 WordNode[1] (13:38-13:47, 525-534)
    │   │   └─0 TextNode "mainframe" (13:38-13:47, 525-534)
    │   ├─16 WhiteSpaceNode " " (13:47-13:48, 534-535)
    │   ├─17 WordNode[1] (13:48-13:56, 535-543)
    │   │   └─0 TextNode "computer" (13:48-13:56, 535-543)
    │   ├─18 WhiteSpaceNode " " (13:56-13:57, 543-544)
    │   ├─19 WordNode[1] (13:57-13:64, 544-551)
    │   │   └─0 TextNode "systems" (13:57-13:64, 544-551)
    │   ├─20 WhiteSpaceNode " " (13:64-13:65, 551-552)
    │   ├─21 WordNode[1] (13:65-13:70, 552-557)
    │   │   └─0 TextNode "doing" (13:65-13:70, 552-557)
    │   ├─22 WhiteSpaceNode " " (13:70-13:71, 557-558)
    │   ├─23 WordNode[1] (13:71-13:76, 558-563)
    │   │   └─0 TextNode "batch" (13:71-13:76, 558-563)
    │   ├─24 WhiteSpaceNode " " (13:76-13:77, 563-564)
    │   ├─25 WordNode[1] (13:77-13:87, 564-574)
    │   │   └─0 TextNode "processing" (13:77-13:87, 564-574)
    │   ├─26 PunctuationNode "," (13:87-13:88, 574-575)
    │   ├─27 WhiteSpaceNode " " (13:88-13:89, 575-576)
    │   ├─28 WordNode[1] (13:89-13:91, 576-578)
    │   │   └─0 TextNode "so" (13:89-13:91, 576-578)
    │   ├─29 WhiteSpaceNode " " (13:91-13:92, 578-579)
    │   ├─30 WordNode[1] (13:92-13:95, 579-582)
    │   │   └─0 TextNode "the" (13:92-13:95, 579-582)
    │   ├─31 WhiteSpaceNode " " (13:95-13:96, 582-583)
    │   ├─32 WordNode[1] (13:96-13:101, 583-588)
    │   │   └─0 TextNode "first" (13:96-13:101, 583-588)
    │   ├─33 WhiteSpaceNode " " (13:101-13:102, 588-589)
    │   ├─34 WordNode[1] (13:102-13:107, 589-594)
    │   │   └─0 TextNode "order" (13:102-13:107, 589-594)
    │   ├─35 WhiteSpaceNode " " (13:107-13:108, 594-595)
    │   ├─36 WordNode[1] (13:108-13:110, 595-597)
    │   │   └─0 TextNode "of" (13:108-13:110, 595-597)
    │   ├─37 WhiteSpaceNode " " (13:110-13:111, 597-598)
    │   ├─38 WordNode[1] (13:111-13:119, 598-606)
    │   │   └─0 TextNode "business" (13:111-13:119, 598-606)
    │   ├─39 WhiteSpaceNode " " (13:119-13:120, 606-607)
    │   ├─40 WordNode[1] (13:120-13:123, 607-610)
    │   │   └─0 TextNode "was" (13:120-13:123, 607-610)
    │   ├─41 WhiteSpaceNode " " (13:123-13:124, 610-611)
    │   ├─42 WordNode[1] (13:124-13:126, 611-613)
    │   │   └─0 TextNode "to" (13:124-13:126, 611-613)
    │   ├─43 WhiteSpaceNode " " (13:126-13:127, 613-614)
    │   ├─44 WordNode[1] (13:127-13:130, 614-617)
    │   │   └─0 TextNode "get" (13:127-13:130, 614-617)
    │   ├─45 WhiteSpaceNode " " (13:130-13:131, 617-618)
    │   ├─46 WordNode[1] (13:131-13:132, 618-619)
    │   │   └─0 TextNode "a" (13:131-13:132, 618-619)
    │   ├─47 WhiteSpaceNode " " (13:132-13:133, 619-620)
    │   ├─48 WordNode[3] (13:133-13:139, 620-626)
    │   │   ├─0 TextNode "PDP" (13:133-13:136, 620-623)
    │   │   ├─1 PunctuationNode "-" (13:136-13:137, 623-624)
    │   │   └─2 TextNode "11" (13:137-13:139, 624-626)
    │   ├─49 PunctuationNode "/" (13:139-13:140, 626-627)
    │   ├─50 WordNode[1] (13:140-13:142, 627-629)
    │   │   └─0 TextNode "45" (13:140-13:142, 627-629)
    │   ├─51 WhiteSpaceNode " " (13:142-13:143, 629-630)
    │   ├─52 WordNode[1] (13:143-13:151, 630-638)
    │   │   └─0 TextNode "suitable" (13:143-13:151, 630-638)
    │   ├─53 WhiteSpaceNode " " (13:151-13:152, 638-639)
    │   ├─54 WordNode[1] (13:152-13:155, 639-642)
    │   │   └─0 TextNode "for" (13:152-13:155, 639-642)
    │   ├─55 WhiteSpaceNode " " (13:155-13:156, 642-643)
    │   ├─56 WordNode[1] (13:156-13:163, 643-650)
    │   │   └─0 TextNode "running" (13:156-13:163, 643-650)
    │   ├─57 WhiteSpaceNode " " (13:163-13:164, 650-651)
    │   ├─58 WordNode[1] (13:164-13:168, 651-655)
    │   │   └─0 TextNode "with" (13:164-13:168, 651-655)
    │   ├─59 WhiteSpaceNode " " (13:168-13:169, 655-656)
    │   ├─60 WordNode[1] (13:169-13:172, 656-659)
    │   │   └─0 TextNode "the" (13:169-13:172, 656-659)
    │   ├─61 WhiteSpaceNode " " (13:172-13:173, 659-660)
    │   ├─62 WordNode[3] (13:173-13:185, 660-672)
    │   │   ├─0 TextNode "then" (13:173-13:177, 660-664)
    │   │   ├─1 PunctuationNode "-" (13:177-13:178, 664-665)
    │   │   └─2 TextNode "current" (13:178-13:185, 665-672)
    │   ├─63 WhiteSpaceNode " " (13:185-13:186, 672-673)
    │   ├─64 WordNode[1] (13:186-13:193, 673-680)
    │   │   └─0 TextNode "Version" (13:186-13:193, 673-680)
    │   ├─65 WhiteSpaceNode " " (13:193-13:194, 680-681)
    │   ├─66 WordNode[1] (13:194-13:195, 681-682)
    │   │   └─0 TextNode "4" (13:194-13:195, 681-682)
    │   ├─67 WhiteSpaceNode " " (13:195-13:196, 682-683)
    │   ├─68 WordNode[1] (13:196-13:198, 683-685)
    │   │   └─0 TextNode "of" (13:196-13:198, 683-685)
    │   ├─69 WhiteSpaceNode " " (13:198-13:199, 685-686)
    │   ├─70 WordNode[1] (13:199-13:203, 686-690)
    │   │   └─0 TextNode "Unix" (13:199-13:203, 686-690)
    │   └─71 PunctuationNode "." (13:203-13:204, 690-691)
    ├─1 WhiteSpaceNode " " (13:204-13:205, 691-692)
    └─2 SentenceNode[85] (13:205-13:473, 692-960)
        ├─0 WordNode[1] (13:205-13:208, 692-695)
        │   └─0 TextNode "The" (13:205-13:208, 692-695)
        ├─1 WhiteSpaceNode " " (13:208-13:209, 695-696)
        ├─2 WordNode[1] (13:209-13:217, 696-704)
        │   └─0 TextNode "Computer" (13:209-13:217, 696-704)
        ├─3 WhiteSpaceNode " " (13:217-13:218, 704-705)
        ├─4 WordNode[1] (13:218-13:225, 705-712)
        │   └─0 TextNode "Science" (13:218-13:225, 705-712)
        ├─5 WhiteSpaceNode " " (13:225-13:226, 712-713)
        ├─6 WordNode[1] (13:226-13:236, 713-723)
        │   └─0 TextNode "Department" (13:226-13:236, 713-723)
        ├─7 WhiteSpaceNode " " (13:236-13:237, 723-724)
        ├─8 WordNode[1] (13:237-13:239, 724-726)
        │   └─0 TextNode "at" (13:237-13:239, 724-726)
        ├─9 WhiteSpaceNode " " (13:239-13:240, 726-727)
        ├─10 WordNode[1] (13:240-13:248, 727-735)
        │   └─0 TextNode "Berkeley" (13:240-13:248, 727-735)
        ├─11 PunctuationNode "," (13:248-13:249, 735-736)
        ├─12 WhiteSpaceNode " " (13:249-13:250, 736-737)
        ├─13 WordNode[1] (13:250-13:258, 737-745)
        │   └─0 TextNode "together" (13:250-13:258, 737-745)
        ├─14 WhiteSpaceNode " " (13:258-13:259, 745-746)
        ├─15 WordNode[1] (13:259-13:263, 746-750)
        │   └─0 TextNode "with" (13:259-13:263, 746-750)
        ├─16 WhiteSpaceNode " " (13:263-13:264, 750-751)
        ├─17 WordNode[1] (13:264-13:267, 751-754)
        │   └─0 TextNode "the" (13:264-13:267, 751-754)
        ├─18 WhiteSpaceNode " " (13:267-13:268, 754-755)
        ├─19 WordNode[1] (13:268-13:279, 755-766)
        │   └─0 TextNode "Mathematics" (13:268-13:279, 755-766)
        ├─20 WhiteSpaceNode " " (13:279-13:280, 766-767)
        ├─21 WordNode[1] (13:280-13:290, 767-777)
        │   └─0 TextNode "Department" (13:280-13:290, 767-777)
        ├─22 WhiteSpaceNode " " (13:290-13:291, 777-778)
        ├─23 WordNode[1] (13:291-13:294, 778-781)
        │   └─0 TextNode "and" (13:291-13:294, 778-781)
        ├─24 WhiteSpaceNode " " (13:294-13:295, 781-782)
        ├─25 WordNode[1] (13:295-13:298, 782-785)
        │   └─0 TextNode "the" (13:295-13:298, 782-785)
        ├─26 WhiteSpaceNode " " (13:298-13:299, 785-786)
        ├─27 WordNode[1] (13:299-13:309, 786-796)
        │   └─0 TextNode "Statistics" (13:299-13:309, 786-796)
        ├─28 WhiteSpaceNode " " (13:309-13:310, 796-797)
        ├─29 WordNode[1] (13:310-13:320, 797-807)
        │   └─0 TextNode "Department" (13:310-13:320, 797-807)
        ├─30 PunctuationNode "," (13:320-13:321, 807-808)
        ├─31 WhiteSpaceNode " " (13:321-13:322, 808-809)
        ├─32 WordNode[1] (13:322-13:326, 809-813)
        │   └─0 TextNode "were" (13:322-13:326, 809-813)
        ├─33 WhiteSpaceNode " " (13:326-13:327, 813-814)
        ├─34 WordNode[1] (13:327-13:331, 814-818)
        │   └─0 TextNode "able" (13:327-13:331, 814-818)
        ├─35 WhiteSpaceNode " " (13:331-13:332, 818-819)
        ├─36 WordNode[1] (13:332-13:334, 819-821)
        │   └─0 TextNode "to" (13:332-13:334, 819-821)
        ├─37 WhiteSpaceNode " " (13:334-13:335, 821-822)
        ├─38 WordNode[1] (13:335-13:342, 822-829)
        │   └─0 TextNode "jointly" (13:335-13:342, 822-829)
        ├─39 WhiteSpaceNode " " (13:342-13:343, 829-830)
        ├─40 WordNode[1] (13:343-13:351, 830-838)
        │   └─0 TextNode "purchase" (13:343-13:351, 830-838)
        ├─41 WhiteSpaceNode " " (13:351-13:352, 838-839)
        ├─42 WordNode[1] (13:352-13:353, 839-840)
        │   └─0 TextNode "a" (13:352-13:353, 839-840)
        ├─43 WhiteSpaceNode " " (13:353-13:354, 840-841)
        ├─44 WordNode[3] (13:354-13:360, 841-847)
        │   ├─0 TextNode "PDP" (13:354-13:357, 841-844)
        │   ├─1 PunctuationNode "-" (13:357-13:358, 844-845)
        │   └─2 TextNode "11" (13:358-13:360, 845-847)
        ├─45 PunctuationNode "/" (13:360-13:361, 847-848)
        ├─46 WordNode[2] (13:361-13:364, 848-851)
        │   ├─0 TextNode "45" (13:361-13:363, 848-850)
        │   └─1 PunctuationNode "." (13:363-13:364, 850-851)
        ├─47 WhiteSpaceNode " " (13:364-13:365, 851-852)
        ├─48 WordNode[1] (13:365-13:367, 852-854)
        │   └─0 TextNode "In" (13:365-13:367, 852-854)
        ├─49 WhiteSpaceNode " " (13:367-13:368, 854-855)
        ├─50 WordNode[1] (13:368-13:375, 855-862)
        │   └─0 TextNode "January" (13:368-13:375, 855-862)
        ├─51 WhiteSpaceNode " " (13:375-13:376, 862-863)
        ├─52 WordNode[1] (13:376-13:380, 863-867)
        │   └─0 TextNode "1974" (13:376-13:380, 863-867)
        ├─53 PunctuationNode "," (13:380-13:381, 867-868)
        ├─54 WhiteSpaceNode " " (13:381-13:382, 868-869)
        ├─55 WordNode[1] (13:382-13:383, 869-870)
        │   └─0 TextNode "a" (13:382-13:383, 869-870)
        ├─56 WhiteSpaceNode " " (13:383-13:384, 870-871)
        ├─57 WordNode[1] (13:384-13:391, 871-878)
        │   └─0 TextNode "Version" (13:384-13:391, 871-878)
        ├─58 WhiteSpaceNode " " (13:391-13:392, 878-879)
        ├─59 WordNode[1] (13:392-13:393, 879-880)
        │   └─0 TextNode "4" (13:392-13:393, 879-880)
        ├─60 WhiteSpaceNode " " (13:393-13:394, 880-881)
        ├─61 WordNode[1] (13:394-13:398, 881-885)
        │   └─0 TextNode "tape" (13:394-13:398, 881-885)
        ├─62 WhiteSpaceNode " " (13:398-13:399, 885-886)
        ├─63 WordNode[1] (13:399-13:402, 886-889)
        │   └─0 TextNode "was" (13:399-13:402, 886-889)
        ├─64 WhiteSpaceNode " " (13:402-13:403, 889-890)
        ├─65 WordNode[1] (13:403-13:412, 890-899)
        │   └─0 TextNode "delivered" (13:403-13:412, 890-899)
        ├─66 WhiteSpaceNode " " (13:412-13:413, 899-900)
        ├─67 WordNode[1] (13:413-13:416, 900-903)
        │   └─0 TextNode "and" (13:413-13:416, 900-903)
        ├─68 WhiteSpaceNode " " (13:416-13:417, 903-904)
        ├─69 WordNode[1] (13:417-13:421, 904-908)
        │   └─0 TextNode "Unix" (13:417-13:421, 904-908)
        ├─70 WhiteSpaceNode " " (13:421-13:422, 908-909)
        ├─71 WordNode[1] (13:422-13:425, 909-912)
        │   └─0 TextNode "was" (13:422-13:425, 909-912)
        ├─72 WhiteSpaceNode " " (13:425-13:426, 912-913)
        ├─73 WordNode[1] (13:426-13:435, 913-922)
        │   └─0 TextNode "installed" (13:426-13:435, 913-922)
        ├─74 WhiteSpaceNode " " (13:435-13:436, 922-923)
        ├─75 WordNode[1] (13:436-13:438, 923-925)
        │   └─0 TextNode "by" (13:436-13:438, 923-925)
        ├─76 WhiteSpaceNode " " (13:438-13:439, 925-926)
        ├─77 WordNode[1] (13:439-13:447, 926-934)
        │   └─0 TextNode "graduate" (13:439-13:447, 926-934)
        ├─78 WhiteSpaceNode " " (13:447-13:448, 934-935)
        ├─79 WordNode[1] (13:448-13:455, 935-942)
        │   └─0 TextNode "student" (13:448-13:455, 935-942)
        ├─80 WhiteSpaceNode " " (13:455-13:456, 942-943)
        ├─81 WordNode[1] (13:456-13:461, 943-948)
        │   └─0 TextNode "Keith" (13:456-13:461, 943-948)
        ├─82 WhiteSpaceNode " " (13:461-13:462, 948-949)
        ├─83 WordNode[1] (13:462-13:472, 949-959)
        │   └─0 TextNode "Standiford" (13:462-13:472, 949-959)
        └─84 PunctuationNode "." (13:472-13:473, 959-960)
$

*1:お気づきの方もいらっしゃるでしょうが、 このテキスト・データはオライリーの "Open Sources: Voices from the Open Source Revolution" の第3章 "Twenty Years of Berkeley Unix: From AT&T-Owned to Freely Redistributable" の冒頭部分です(笑)

Remark(3)remark-retext: プラグイン・チェインの接続点

remark-retext: connection points for plugin chains


2020/08/30
藤田昭人


前回、 ファイルから Markdown データを読み込み(remark-parse)、 Text データへと変換したのち(remark-retext)、 文字列化する(retext-stringify)プログラムを紹介しました。 各々が生成する構文木mdastnlcst)を表示するプラグインを作って データ構造を確認してみましたが、 Markdown データの構文木はパラグラフ単位であるのに対し、 Text データの構文木トークン単位で管理していることがわかりました。 本稿では mdastからnlcst、 すなわち Markdown から Text への変換を行っている remark-retext について深堀りしてみます。

というのも、この remark-retext のどこかに、 パラグラフ・トークン変換を行うトークナイザーが組み込まれているはずだからです。 ご存知のとおり、単語間に空白が存在する英文のトークナイザーは単純に実装できますが、 日本文の場合には形態素解析が必要となります。 形態素解析機そのものは MeCab などのオープンソース・ソフトウェアが流通してますが、 それを Remark に組み込んで日本文を含むドキュメントでも Remark を齟齬なく動作させるには、 Remark でのパラグラフ -- トークン変換の仕組みを理解しておく必要があります。

つまり、本稿は Remark 日本語化の第1段階ということになります。


unist-util-inspect:構文木をコンパクトに

本論に入るまでに Tips をひとつ。
構文木デバッグ・プリントは非常に冗長な表示になってしましますが、 unist-util-inspectを使うと多少マシになります。 例えばこんな感じ…

var inspect = require('unist-util-inspect');

function attacher0() {
  return transformer;
  function transformer(tree, file) {
    console.log("\n### remark");
    console.log(inspect(tree));
  }
}

function attacher1() {
  return transformer;
  function transformer(tree, file) {
    console.log("\n### retext");
    console.log(inspect(tree));
  }
}

function attacher2() {
  return transformer;
  function transformer(tree, file) {
    console.log("\n### after retext-pos");
    console.log(inspect(tree));
  }
}

var unified = require('unified');
var parse = require('remark-parse');
var remark2retext = require('remark-retext');
var english = require('parse-english');
var stringify = require('retext-stringify');
var vfile = require('to-vfile');
var report = require('vfile-reporter');
var pos = require('retext-pos');

var processor = unified()
    .use(parse)
    .use(attacher0)
    .use(remark2retext, english)
    .use(attacher1)
    .use(pos)
    .use(attacher2)
    .use(stringify);

processor.process(vfile.readSync('english.md'), done);

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

このソースを動かすには必要なパッケージをインストールしてもらって…

$ npm install unist-util-inspect unified remark-parse remark-retext parse-english retext-stringify to-vfile vfile-reporter retext-pos

 ・・・

$ node example.js

…このソースの実行例は長いので 末尾 に掲載します。

ソース自体は 前回 紹介したものと基本的には同じですが、プラグイン・チェインに 品詞タグを付与するretext-posを追加しています。詳細は 末尾 の実行例を見てもらうとわかると思います。


remark-retext:Markdown から Text への変換

それでは本題のremark-retextを紹介しましょう。

このプラグインが用意しているAPIは次に示すエントリーだけです。

origin.use(remark2retext, destination[, options])

remarkmdastプラグインbridgeするか、 あるいはretextnlcst)にmutate します。

destination

destination はパーサまたはプロセッサのいずれかです。

Unified プロセッサが与えられた場合、 新しい nlcst ツリーで destination プロセッサを実行し、 実行後にそのツリーを破棄して元のツリーで元のプロセッサを実行し続けます (bridge モード)。

パーサー (parse-latinparse-englishparse-dutch など)が指定されている場合、 構文木を他のプラグインに渡します (mutate モード)。

この「destination はパーサーまたはプロセッサのいずれかです」が分かりにくいのですが…

つまり remark-retext は、単純な構文木の変換を行うmutate モードと、 プロセッサーを新たに生成しプラグイン・チェインを分岐させる bridge モードの 2つのモードをサポートしているということのようです。

実は上記の3種類のパーサー parse-latinparse-englishparse-dutch には各々、 プラグイン形式(すなわち use を使ってプラグイン・チェインに登録できる形式) retext-latinretext-englishretext-dutch も用意されています(前回を参照)。

mutate モードで使用する場合には第2引数の destination にパーサーそのものを渡して構文木の変換のみを行いますが、 bridge モードで使用する場合には新たなプロセッサーを生成して 第2引数の destination に渡し新たなプラグイン・チェインを生成します。 その場合は processor.useプラグイン形式のパーサーを設定します。

例えば、前述のソースはmutate モードを使ってますが、 これをbridge モードに書き直すと…

var unified = require('unified')
var markdown = require('remark-parse')
var remark2retext = require('remark-retext')
var english = require('retext-english')
var stringify = require('retext-stringify');

var inspect = require('unist-util-inspect');

function attacher() {
  return transformer;
  function transformer(tree, file) {
    console.log(inspect(tree));
  }
}

var processor = unified()
  .use(markdown)
  .use(remark2retext, 
    unified()
    .use(english)
    .use(attacher))
  .use(stringify);

processor.process(vfile.readSync('example.md'), function (error, file) {
});

…といった感じになります。

ソースコード

幾つかテスト・プログラムを書いて上記の仕様を確認したのですが、 結局プラグインの間で引き渡される構文木の扱い方がよくわからなかったので remark-retextソースコードをあたってみました。 このプラグインの実装は非常にコンパクトなので、 以下に全文を引用します。

'use strict'

var mdast2nlcst = require('mdast-util-to-nlcst')

module.exports = remark2retext

// Attacher.
// If a destination processor is given, runs the destination 
// with the new nlcst tree (bridge mode).
// If a parser is given, returns the nlcst tree: 
// further plugins run on that tree (mutate mode).
function remark2retext(destination, options) {
  var fn = destination && destination.run ? bridge : mutate
  return fn(destination, options)
}

// Mutate mode.
// Further transformers run on the nlcst tree.
function mutate(parser, options) {
  return transformer
  function transformer(node, file) {
    return mdast2nlcst(node, file, parser, options)
  }
}

// Bridge mode.
// Runs the destination with the new nlcst tree.
function bridge(destination, options) {
  return transformer
  function transformer(node, file, next) {
    var Parser = destination.freeze().Parser
    var tree = mdast2nlcst(node, file, Parser, options)
    destination.run(tree, file, done)
    function done(err) {
      next(err)
    }
  }
}

ソースコード から分かることは以下の3点です。


どうやら remark-retextmdast 構文木からnlcst 構文木への複製を行う mdast-util-to-nlcst を unified フレームワークに組み入れるインターフェースのようです。

次は、構文木の複製を実装している mdast-util-to-nlcst を追っかけてみます。

以上



$ node example.js

### remark
root[2] (1:1-4:1, 0-140)
├─0 heading[1] (1:1-1:24, 0-23)
│   │ depth: 1
│   └─0 text "in the United States" (1:3-1:23, 2-22)
└─1 paragraph[1] (3:1-3:115, 25-139)
    └─0 text "The project will cost between $5,000 and $30,000 dollars, depending on how fancy you want the final product to be." (3:1-3:115, 25-139)

### retext
RootNode[3] (1:3-3:115, 2-139)
├─0 ParagraphNode[1] (1:3-1:23, 2-22)
│   └─0 SentenceNode[7] (1:3-1:23, 2-22)
│       ├─0 WordNode[1] (1:3-1:5, 2-4)
│       │   └─0 TextNode "in" (1:3-1:5, 2-4)
│       ├─1 WhiteSpaceNode " " (1:5-1:6, 4-5)
│       ├─2 WordNode[1] (1:6-1:9, 5-8)
│       │   └─0 TextNode "the" (1:6-1:9, 5-8)
│       ├─3 WhiteSpaceNode " " (1:9-1:10, 8-9)
│       ├─4 WordNode[1] (1:10-1:16, 9-15)
│       │   └─0 TextNode "United" (1:10-1:16, 9-15)
│       ├─5 WhiteSpaceNode " " (1:16-1:17, 15-16)
│       └─6 WordNode[1] (1:17-1:23, 16-22)
│           └─0 TextNode "States" (1:17-1:23, 16-22)
├─1 WhiteSpaceNode "\n\n" (1:24-3:1, 23-25)
└─2 ParagraphNode[1] (3:1-3:115, 25-139)
    └─0 SentenceNode[47] (3:1-3:115, 25-139)
        ├─0 WordNode[1] (3:1-3:4, 25-28)
        │   └─0 TextNode "The" (3:1-3:4, 25-28)
        ├─1 WhiteSpaceNode " " (3:4-3:5, 28-29)
        ├─2 WordNode[1] (3:5-3:12, 29-36)
        │   └─0 TextNode "project" (3:5-3:12, 29-36)
        ├─3 WhiteSpaceNode " " (3:12-3:13, 36-37)
        ├─4 WordNode[1] (3:13-3:17, 37-41)
        │   └─0 TextNode "will" (3:13-3:17, 37-41)
        ├─5 WhiteSpaceNode " " (3:17-3:18, 41-42)
        ├─6 WordNode[1] (3:18-3:22, 42-46)
        │   └─0 TextNode "cost" (3:18-3:22, 42-46)
        ├─7 WhiteSpaceNode " " (3:22-3:23, 46-47)
        ├─8 WordNode[1] (3:23-3:30, 47-54)
        │   └─0 TextNode "between" (3:23-3:30, 47-54)
        ├─9 WhiteSpaceNode " " (3:30-3:31, 54-55)
        ├─10 SymbolNode "$" (3:31-3:32, 55-56)
        ├─11 WordNode[1] (3:32-3:33, 56-57)
        │   └─0 TextNode "5" (3:32-3:33, 56-57)
        ├─12 PunctuationNode "," (3:33-3:34, 57-58)
        ├─13 WordNode[1] (3:34-3:37, 58-61)
        │   └─0 TextNode "000" (3:34-3:37, 58-61)
        ├─14 WhiteSpaceNode " " (3:37-3:38, 61-62)
        ├─15 WordNode[1] (3:38-3:41, 62-65)
        │   └─0 TextNode "and" (3:38-3:41, 62-65)
        ├─16 WhiteSpaceNode " " (3:41-3:42, 65-66)
        ├─17 SymbolNode "$" (3:42-3:43, 66-67)
        ├─18 WordNode[1] (3:43-3:45, 67-69)
        │   └─0 TextNode "30" (3:43-3:45, 67-69)
        ├─19 PunctuationNode "," (3:45-3:46, 69-70)
        ├─20 WordNode[1] (3:46-3:49, 70-73)
        │   └─0 TextNode "000" (3:46-3:49, 70-73)
        ├─21 WhiteSpaceNode " " (3:49-3:50, 73-74)
        ├─22 WordNode[1] (3:50-3:57, 74-81)
        │   └─0 TextNode "dollars" (3:50-3:57, 74-81)
        ├─23 PunctuationNode "," (3:57-3:58, 81-82)
        ├─24 WhiteSpaceNode " " (3:58-3:59, 82-83)
        ├─25 WordNode[1] (3:59-3:68, 83-92)
        │   └─0 TextNode "depending" (3:59-3:68, 83-92)
        ├─26 WhiteSpaceNode " " (3:68-3:69, 92-93)
        ├─27 WordNode[1] (3:69-3:71, 93-95)
        │   └─0 TextNode "on" (3:69-3:71, 93-95)
        ├─28 WhiteSpaceNode " " (3:71-3:72, 95-96)
        ├─29 WordNode[1] (3:72-3:75, 96-99)
        │   └─0 TextNode "how" (3:72-3:75, 96-99)
        ├─30 WhiteSpaceNode " " (3:75-3:76, 99-100)
        ├─31 WordNode[1] (3:76-3:81, 100-105)
        │   └─0 TextNode "fancy" (3:76-3:81, 100-105)
        ├─32 WhiteSpaceNode " " (3:81-3:82, 105-106)
        ├─33 WordNode[1] (3:82-3:85, 106-109)
        │   └─0 TextNode "you" (3:82-3:85, 106-109)
        ├─34 WhiteSpaceNode " " (3:85-3:86, 109-110)
        ├─35 WordNode[1] (3:86-3:90, 110-114)
        │   └─0 TextNode "want" (3:86-3:90, 110-114)
        ├─36 WhiteSpaceNode " " (3:90-3:91, 114-115)
        ├─37 WordNode[1] (3:91-3:94, 115-118)
        │   └─0 TextNode "the" (3:91-3:94, 115-118)
        ├─38 WhiteSpaceNode " " (3:94-3:95, 118-119)
        ├─39 WordNode[1] (3:95-3:100, 119-124)
        │   └─0 TextNode "final" (3:95-3:100, 119-124)
        ├─40 WhiteSpaceNode " " (3:100-3:101, 124-125)
        ├─41 WordNode[1] (3:101-3:108, 125-132)
        │   └─0 TextNode "product" (3:101-3:108, 125-132)
        ├─42 WhiteSpaceNode " " (3:108-3:109, 132-133)
        ├─43 WordNode[1] (3:109-3:111, 133-135)
        │   └─0 TextNode "to" (3:109-3:111, 133-135)
        ├─44 WhiteSpaceNode " " (3:111-3:112, 135-136)
        ├─45 WordNode[1] (3:112-3:114, 136-138)
        │   └─0 TextNode "be" (3:112-3:114, 136-138)
        └─46 PunctuationNode "." (3:114-3:115, 138-139)

### after retext-pos
RootNode[3] (1:3-3:115, 2-139)
├─0 ParagraphNode[1] (1:3-1:23, 2-22)
│   └─0 SentenceNode[7] (1:3-1:23, 2-22)
│       ├─0 WordNode[1] (1:3-1:5, 2-4)
│       │   │ data: {"partOfSpeech":"IN"}
│       │   └─0 TextNode "in" (1:3-1:5, 2-4)
│       ├─1 WhiteSpaceNode " " (1:5-1:6, 4-5)
│       ├─2 WordNode[1] (1:6-1:9, 5-8)
│       │   │ data: {"partOfSpeech":"DT"}
│       │   └─0 TextNode "the" (1:6-1:9, 5-8)
│       ├─3 WhiteSpaceNode " " (1:9-1:10, 8-9)
│       ├─4 WordNode[1] (1:10-1:16, 9-15)
│       │   │ data: {"partOfSpeech":"VBN"}
│       │   └─0 TextNode "United" (1:10-1:16, 9-15)
│       ├─5 WhiteSpaceNode " " (1:16-1:17, 15-16)
│       └─6 WordNode[1] (1:17-1:23, 16-22)
│           │ data: {"partOfSpeech":"NNPS"}
│           └─0 TextNode "States" (1:17-1:23, 16-22)
├─1 WhiteSpaceNode "\n\n" (1:24-3:1, 23-25)
└─2 ParagraphNode[1] (3:1-3:115, 25-139)
    └─0 SentenceNode[47] (3:1-3:115, 25-139)
        ├─0 WordNode[1] (3:1-3:4, 25-28)
        │   │ data: {"partOfSpeech":"DT"}
        │   └─0 TextNode "The" (3:1-3:4, 25-28)
        ├─1 WhiteSpaceNode " " (3:4-3:5, 28-29)
        ├─2 WordNode[1] (3:5-3:12, 29-36)
        │   │ data: {"partOfSpeech":"NN"}
        │   └─0 TextNode "project" (3:5-3:12, 29-36)
        ├─3 WhiteSpaceNode " " (3:12-3:13, 36-37)
        ├─4 WordNode[1] (3:13-3:17, 37-41)
        │   │ data: {"partOfSpeech":"MD"}
        │   └─0 TextNode "will" (3:13-3:17, 37-41)
        ├─5 WhiteSpaceNode " " (3:17-3:18, 41-42)
        ├─6 WordNode[1] (3:18-3:22, 42-46)
        │   │ data: {"partOfSpeech":"NN"}
        │   └─0 TextNode "cost" (3:18-3:22, 42-46)
        ├─7 WhiteSpaceNode " " (3:22-3:23, 46-47)
        ├─8 WordNode[1] (3:23-3:30, 47-54)
        │   │ data: {"partOfSpeech":"IN"}
        │   └─0 TextNode "between" (3:23-3:30, 47-54)
        ├─9 WhiteSpaceNode " " (3:30-3:31, 54-55)
        ├─10 SymbolNode "$" (3:31-3:32, 55-56)
        ├─11 WordNode[1] (3:32-3:33, 56-57)
        │   │ data: {"partOfSpeech":"CD"}
        │   └─0 TextNode "5" (3:32-3:33, 56-57)
        ├─12 PunctuationNode "," (3:33-3:34, 57-58)
        ├─13 WordNode[1] (3:34-3:37, 58-61)
        │   │ data: {"partOfSpeech":"CD"}
        │   └─0 TextNode "000" (3:34-3:37, 58-61)
        ├─14 WhiteSpaceNode " " (3:37-3:38, 61-62)
        ├─15 WordNode[1] (3:38-3:41, 62-65)
        │   │ data: {"partOfSpeech":"CC"}
        │   └─0 TextNode "and" (3:38-3:41, 62-65)
        ├─16 WhiteSpaceNode " " (3:41-3:42, 65-66)
        ├─17 SymbolNode "$" (3:42-3:43, 66-67)
        ├─18 WordNode[1] (3:43-3:45, 67-69)
        │   │ data: {"partOfSpeech":"CD"}
        │   └─0 TextNode "30" (3:43-3:45, 67-69)
        ├─19 PunctuationNode "," (3:45-3:46, 69-70)
        ├─20 WordNode[1] (3:46-3:49, 70-73)
        │   │ data: {"partOfSpeech":"CD"}
        │   └─0 TextNode "000" (3:46-3:49, 70-73)
        ├─21 WhiteSpaceNode " " (3:49-3:50, 73-74)
        ├─22 WordNode[1] (3:50-3:57, 74-81)
        │   │ data: {"partOfSpeech":"NNS"}
        │   └─0 TextNode "dollars" (3:50-3:57, 74-81)
        ├─23 PunctuationNode "," (3:57-3:58, 81-82)
        ├─24 WhiteSpaceNode " " (3:58-3:59, 82-83)
        ├─25 WordNode[1] (3:59-3:68, 83-92)
        │   │ data: {"partOfSpeech":"VBG"}
        │   └─0 TextNode "depending" (3:59-3:68, 83-92)
        ├─26 WhiteSpaceNode " " (3:68-3:69, 92-93)
        ├─27 WordNode[1] (3:69-3:71, 93-95)
        │   │ data: {"partOfSpeech":"IN"}
        │   └─0 TextNode "on" (3:69-3:71, 93-95)
        ├─28 WhiteSpaceNode " " (3:71-3:72, 95-96)
        ├─29 WordNode[1] (3:72-3:75, 96-99)
        │   │ data: {"partOfSpeech":"WRB"}
        │   └─0 TextNode "how" (3:72-3:75, 96-99)
        ├─30 WhiteSpaceNode " " (3:75-3:76, 99-100)
        ├─31 WordNode[1] (3:76-3:81, 100-105)
        │   │ data: {"partOfSpeech":"JJ"}
        │   └─0 TextNode "fancy" (3:76-3:81, 100-105)
        ├─32 WhiteSpaceNode " " (3:81-3:82, 105-106)
        ├─33 WordNode[1] (3:82-3:85, 106-109)
        │   │ data: {"partOfSpeech":"PRP"}
        │   └─0 TextNode "you" (3:82-3:85, 106-109)
        ├─34 WhiteSpaceNode " " (3:85-3:86, 109-110)
        ├─35 WordNode[1] (3:86-3:90, 110-114)
        │   │ data: {"partOfSpeech":"VBP"}
        │   └─0 TextNode "want" (3:86-3:90, 110-114)
        ├─36 WhiteSpaceNode " " (3:90-3:91, 114-115)
        ├─37 WordNode[1] (3:91-3:94, 115-118)
        │   │ data: {"partOfSpeech":"DT"}
        │   └─0 TextNode "the" (3:91-3:94, 115-118)
        ├─38 WhiteSpaceNode " " (3:94-3:95, 118-119)
        ├─39 WordNode[1] (3:95-3:100, 119-124)
        │   │ data: {"partOfSpeech":"JJ"}
        │   └─0 TextNode "final" (3:95-3:100, 119-124)
        ├─40 WhiteSpaceNode " " (3:100-3:101, 124-125)
        ├─41 WordNode[1] (3:101-3:108, 125-132)
        │   │ data: {"partOfSpeech":"NN"}
        │   └─0 TextNode "product" (3:101-3:108, 125-132)
        ├─42 WhiteSpaceNode " " (3:108-3:109, 132-133)
        ├─43 WordNode[1] (3:109-3:111, 133-135)
        │   │ data: {"partOfSpeech":"TO"}
        │   └─0 TextNode "to" (3:109-3:111, 133-135)
        ├─44 WhiteSpaceNode " " (3:111-3:112, 135-136)
        ├─45 WordNode[1] (3:112-3:114, 136-138)
        │   │ data: {"partOfSpeech":"VB"}
        │   └─0 TextNode "be" (3:112-3:114, 136-138)
        └─46 PunctuationNode "." (3:114-3:115, 138-139)
$

Remark(2)プラグイン の作り方

Creating a plugin with unified


2020/08/27
藤田昭人


Remark の話を続けます。

ビルディング・ブロックを積み上げて所望の機能を得る Remark では、 やはりプラグインを作らなければ、 その有り難みを本格的に感じることはできないのだろうなぁ…と思います。

ということで、本稿ではプラグインの作り方を調べてみました。


Creating a plugin with unified

Remark のプラグインの作り方は Creating a plugin with unified で紹介されています。

が、このチュートリアルも、 ステップ・バイ・ステップで進行するので少々まどろっこしいところがありますので、 その要点を網羅して1本のソースにまとめました。 見ての通り、前半がプラグイン、後半がそれを呼び出す本体のコードです。

/* 
 * 以降がプラグイン
 */
var visit = require('unist-util-visit');
var is = require('unist-util-is');

function attacher() {
    return transformer;

  function transformer(tree, file) {
    visit(tree, 'ParagraphNode', visitor);

    function visitor(node) {
      var children = node.children;

      children.forEach(function(child, index) {
        if (is(children[index - 1], 'SentenceNode') &&
            is(child, 'WhiteSpaceNode') &&
            is(children[index + 1], 'SentenceNode')) {
          if (child.value.length !== 1) {
              file.message('Expected 1 space between sentences, not '
                           +child.value.length, child);
          }
        }
      });
    }
  }
}

var spacing = attacher;

/* 
 * 以降が本体
 */

var fs = require('fs');
var retext = require('retext');
var report = require('vfile-reporter');

var doc = fs.readFileSync('example.md');

retext()
  .use(spacing)
  .process(doc, function(err, file) {
    console.error(report(err || file));
  });

実行すると…

$ node example.js
3:14-3:16  warning  Expected 1 space between sentences, not 21 warning

このプログラムは「文と文の間の空白をチェックし、空白1文字でなければワーニングメッセージを表示」します。プラグインのデモンストレーション以上の意味は無さそうです。


プラグインのインターフェースについて

JavaScriptは引数の記述の自由度プラグインのインターフェース仕様がきになったので、調べてみました。Remark のプラグインのインターフェースはUnifiedのプラグインの項目で確認することができます。

プラグインのインターフェースに関連することだけを次に抜粋しておきます。

プラグイン は、以下の方法で適用されるプロセッサを設定します。

プラグインはコンセプトです。 それらはattacherとして具現化します。


function attacher([options])

Attacher は実体化されたプラグインです。 attacher は、オプションを受け取り、プロセッサを設定することができる関数です。

Attacherは、パーサコンパイラ、設定データ構文木ファイルの処理方法を指定することで、 プロセッサを変更します。

コンテキスト

コンテキスト・オブジェクト(this)は、attacher が適用されているプロセッサに設定されます。

パラメーター
  • options (*, optional) - コンフィグレーション
リターン

transformer - オプション

ノート

Attachers are called when the processor is frozen, not when they are applied.

Attacherは、プロセッサがフリーズしたときに呼び出されるのであって、 適用されたときに呼び出されるのではありません。


function transformer(node, file[, next])

Transformersは、構文木ファイルを処理します。 transformer は、構文木とファイルが実行フェーズに渡されるたびに呼び出される関数です。 エラーが発生した場合(thrown、returned、rejected、passed to next、のいずれかの理由で) プロセスは停止します。

trough による 実行フェーズ は処理されます。 これらの関数の正確なセマンティクスについては、そのドキュメントを参照してください。

パラメーター
リターン
  • void — 何も返されない場合,次の変換器は同じ木を使い続けます.
  • Error — 致命的なエラーでプロセスを停止します。
  • node (Node) — 新しい構文木。 返された場合、次の変換器にはこの新しい木が与えられます。
  • Promise — 非同期操作を実行するために返されます。 promiseは(オプションでNodeを使って)解決されるか、 (オプションでErrorを使って)拒否されなければなりません


function next(err[, tree[, file]])

transformerシグネチャnext (第三引数) が含まれている場合、 transformer は非同期操作を行うことができます。 その場合は next() を呼び出す必要があります

パラメーター
  • err (Error, optional) — プロセスを停止するための致命的なエラー。
  • node (Node, optional) — 新しい構文木。 与えられた場合,次の変換器にはこの新しいツリーが与えられます。
  • file (VFile, optional) — 新しい[ファイル]file]。 与えられた場合,次のtransformerにはこの新しいファイルが与えられます。

どうやら構文木ファイルがセットで引き渡って来るようです。動的に確保され自由度の高いJavaScriptの配列を使った構文木は、もう少し調べてみないと…


構文木についてもう少し深掘り

という事で次のようなデバッグプリントみたいなプラグインを作ってみました。

var visit = require('unist-util-visit')

function attacher() {
    return transformer;
    function transformer(tree, file) {
    visit(tree, visitor);
    function visitor(node) {
        console.log(node);
    }
    }
}

var unified = require('unified');
var parse = require('remark-parse');
var remark2retext = require('remark-retext');
var english = require('parse-english');
var stringify = require('retext-stringify');
var vfile = require('to-vfile');
var report = require('vfile-reporter');

var processor = unified()
    .use(parse)
    .use(attacher)
    .use(remark2retext, english)
    .use(stringify);

processor.process(vfile.readSync('english.md'), done);

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

入力ファイルenglish.mdも用意して…

# in the United States

The project will cost between $5,000 and $30,000 dollars, depending on how fancy you want the final product to be.

…実行してみました。

$ node example.js
{ type: 'root',
  children:
   [ { type: 'heading',
       depth: 1,
       children: [Array],
       position: [Position] },
     { type: 'paragraph', children: [Array], position: [Position] } ],
  position:
   { start: { line: 1, column: 1, offset: 0 },
     end: { line: 4, column: 1, offset: 140 } } }
{ type: 'heading',
  depth: 1,
  children:
   [ { type: 'text',
       value: 'in the United States',
       position: [Position] } ],
  position:
   Position {
     start: { line: 1, column: 1, offset: 0 },
     end: { line: 1, column: 24, offset: 23 },
     indent: [] } }
{ type: 'text',
  value: 'in the United States',
  position:
   Position {
     start: { line: 1, column: 3, offset: 2 },
     end: { line: 1, column: 23, offset: 22 },
     indent: [] } }
{ type: 'paragraph',
  children:
   [ { type: 'text',
       value:
        'The project will cost between $5,000 and $30,000 dollars, depending on how fancy you want the final product to be.',
       position: [Position] } ],
  position:
   Position {
     start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 115, offset: 139 },
     indent: [] } }
{ type: 'text',
  value:
   'The project will cost between $5,000 and $30,000 dollars, depending on how fancy you want the final product to be.',
  position:
   Position {
     start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 115, offset: 139 },
     indent: [] } }
$

Markdownの記法に従って、テキストを切り出してくれるようです。プラグインを挿入するポイントをズラしてみます。

--- example.js-  2020-08-28 08:24:40.000000000 +0900
+++ example.js    2020-08-28 08:24:50.000000000 +0900
@@ -20,8 +20,8 @@

 var processor = unified()
     .use(parse)
-    .use(attacher)
     .use(remark2retext, english)
+    .use(attacher)
     .use(stringify);

 processor.process(vfile.readSync('english.md'), done);

このプログラムを実行すると retext に変換された構文木をダンプすることができます (トークン化された長い構文木が出力されるので末尾に回します)。 テキスト部分がトークン化された長大なツリーが表示されました。が、もちろん、これは英文の場合。 日本文の場合は形態素解析を組み込まないといけませんねぇ。

以上



いや、いきなりトークン化された構文木が出力されたので、 思わず「長げ〜よ」ってツッコンでしまった。

$ node example.js
{ type: 'RootNode',
  children:
   [ { type: 'ParagraphNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: '\n\n', position: [Object] },
     { type: 'ParagraphNode', children: [Array], position: [Object] } ],
  position:
   { start: { line: 1, column: 3, offset: 2 },
     end: { line: 3, column: 115, offset: 139 } } }
{ type: 'ParagraphNode',
  children:
   [ { type: 'SentenceNode', children: [Array], position: [Object] } ],
  position:
   { start: { line: 1, column: 3, offset: 2 },
     end: { line: 1, column: 23, offset: 22 } } }
{ type: 'SentenceNode',
  children:
   [ { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] } ],
  position:
   { start: { line: 1, column: 3, offset: 2 },
     end: { line: 1, column: 23, offset: 22 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'in', position: [Object] } ],
  position:
   { start: { line: 1, column: 3, offset: 2 },
     end: { line: 1, column: 5, offset: 4 } } }
{ type: 'TextNode',
  value: 'in',
  position:
   { start: { line: 1, column: 3, offset: 2 },
     end: { line: 1, column: 5, offset: 4 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 1, column: 5, offset: 4 },
     end: { line: 1, column: 6, offset: 5 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'the', position: [Object] } ],
  position:
   { start: { line: 1, column: 6, offset: 5 },
     end: { line: 1, column: 9, offset: 8 } } }
{ type: 'TextNode',
  value: 'the',
  position:
   { start: { line: 1, column: 6, offset: 5 },
     end: { line: 1, column: 9, offset: 8 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 1, column: 9, offset: 8 },
     end: { line: 1, column: 10, offset: 9 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'United', position: [Object] } ],
  position:
   { start: { line: 1, column: 10, offset: 9 },
     end: { line: 1, column: 16, offset: 15 } } }
{ type: 'TextNode',
  value: 'United',
  position:
   { start: { line: 1, column: 10, offset: 9 },
     end: { line: 1, column: 16, offset: 15 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 1, column: 16, offset: 15 },
     end: { line: 1, column: 17, offset: 16 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'States', position: [Object] } ],
  position:
   { start: { line: 1, column: 17, offset: 16 },
     end: { line: 1, column: 23, offset: 22 } } }
{ type: 'TextNode',
  value: 'States',
  position:
   { start: { line: 1, column: 17, offset: 16 },
     end: { line: 1, column: 23, offset: 22 } } }
{ type: 'WhiteSpaceNode',
  value: '\n\n',
  position:
   { start: { line: 1, column: 24, offset: 23 },
     end: { line: 3, column: 1, offset: 25 } } }
{ type: 'ParagraphNode',
  children:
   [ { type: 'SentenceNode', children: [Array], position: [Object] } ],
  position:
   { start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 115, offset: 139 } } }
{ type: 'SentenceNode',
  children:
   [ { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'SymbolNode', value: '$', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'PunctuationNode', value: ',', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'SymbolNode', value: '$', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'PunctuationNode', value: ',', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'PunctuationNode', value: ',', position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'WhiteSpaceNode', value: ' ', position: [Object] },
     { type: 'WordNode', children: [Array], position: [Object] },
     { type: 'PunctuationNode', value: '.', position: [Object] } ],
  position:
   { start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 115, offset: 139 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'The', position: [Object] } ],
  position:
   { start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 4, offset: 28 } } }
{ type: 'TextNode',
  value: 'The',
  position:
   { start: { line: 3, column: 1, offset: 25 },
     end: { line: 3, column: 4, offset: 28 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 4, offset: 28 },
     end: { line: 3, column: 5, offset: 29 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'project', position: [Object] } ],
  position:
   { start: { line: 3, column: 5, offset: 29 },
     end: { line: 3, column: 12, offset: 36 } } }
{ type: 'TextNode',
  value: 'project',
  position:
   { start: { line: 3, column: 5, offset: 29 },
     end: { line: 3, column: 12, offset: 36 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 12, offset: 36 },
     end: { line: 3, column: 13, offset: 37 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'will', position: [Object] } ],
  position:
   { start: { line: 3, column: 13, offset: 37 },
     end: { line: 3, column: 17, offset: 41 } } }
{ type: 'TextNode',
  value: 'will',
  position:
   { start: { line: 3, column: 13, offset: 37 },
     end: { line: 3, column: 17, offset: 41 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 17, offset: 41 },
     end: { line: 3, column: 18, offset: 42 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'cost', position: [Object] } ],
  position:
   { start: { line: 3, column: 18, offset: 42 },
     end: { line: 3, column: 22, offset: 46 } } }
{ type: 'TextNode',
  value: 'cost',
  position:
   { start: { line: 3, column: 18, offset: 42 },
     end: { line: 3, column: 22, offset: 46 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 22, offset: 46 },
     end: { line: 3, column: 23, offset: 47 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'between', position: [Object] } ],
  position:
   { start: { line: 3, column: 23, offset: 47 },
     end: { line: 3, column: 30, offset: 54 } } }
{ type: 'TextNode',
  value: 'between',
  position:
   { start: { line: 3, column: 23, offset: 47 },
     end: { line: 3, column: 30, offset: 54 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 30, offset: 54 },
     end: { line: 3, column: 31, offset: 55 } } }
{ type: 'SymbolNode',
  value: '$',
  position:
   { start: { line: 3, column: 31, offset: 55 },
     end: { line: 3, column: 32, offset: 56 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: '5', position: [Object] } ],
  position:
   { start: { line: 3, column: 32, offset: 56 },
     end: { line: 3, column: 33, offset: 57 } } }
{ type: 'TextNode',
  value: '5',
  position:
   { start: { line: 3, column: 32, offset: 56 },
     end: { line: 3, column: 33, offset: 57 } } }
{ type: 'PunctuationNode',
  value: ',',
  position:
   { start: { line: 3, column: 33, offset: 57 },
     end: { line: 3, column: 34, offset: 58 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: '000', position: [Object] } ],
  position:
   { start: { line: 3, column: 34, offset: 58 },
     end: { line: 3, column: 37, offset: 61 } } }
{ type: 'TextNode',
  value: '000',
  position:
   { start: { line: 3, column: 34, offset: 58 },
     end: { line: 3, column: 37, offset: 61 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 37, offset: 61 },
     end: { line: 3, column: 38, offset: 62 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'and', position: [Object] } ],
  position:
   { start: { line: 3, column: 38, offset: 62 },
     end: { line: 3, column: 41, offset: 65 } } }
{ type: 'TextNode',
  value: 'and',
  position:
   { start: { line: 3, column: 38, offset: 62 },
     end: { line: 3, column: 41, offset: 65 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 41, offset: 65 },
     end: { line: 3, column: 42, offset: 66 } } }
{ type: 'SymbolNode',
  value: '$',
  position:
   { start: { line: 3, column: 42, offset: 66 },
     end: { line: 3, column: 43, offset: 67 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: '30', position: [Object] } ],
  position:
   { start: { line: 3, column: 43, offset: 67 },
     end: { line: 3, column: 45, offset: 69 } } }
{ type: 'TextNode',
  value: '30',
  position:
   { start: { line: 3, column: 43, offset: 67 },
     end: { line: 3, column: 45, offset: 69 } } }
{ type: 'PunctuationNode',
  value: ',',
  position:
   { start: { line: 3, column: 45, offset: 69 },
     end: { line: 3, column: 46, offset: 70 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: '000', position: [Object] } ],
  position:
   { start: { line: 3, column: 46, offset: 70 },
     end: { line: 3, column: 49, offset: 73 } } }
{ type: 'TextNode',
  value: '000',
  position:
   { start: { line: 3, column: 46, offset: 70 },
     end: { line: 3, column: 49, offset: 73 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 49, offset: 73 },
     end: { line: 3, column: 50, offset: 74 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'dollars', position: [Object] } ],
  position:
   { start: { line: 3, column: 50, offset: 74 },
     end: { line: 3, column: 57, offset: 81 } } }
{ type: 'TextNode',
  value: 'dollars',
  position:
   { start: { line: 3, column: 50, offset: 74 },
     end: { line: 3, column: 57, offset: 81 } } }
{ type: 'PunctuationNode',
  value: ',',
  position:
   { start: { line: 3, column: 57, offset: 81 },
     end: { line: 3, column: 58, offset: 82 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 58, offset: 82 },
     end: { line: 3, column: 59, offset: 83 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'depending', position: [Object] } ],
  position:
   { start: { line: 3, column: 59, offset: 83 },
     end: { line: 3, column: 68, offset: 92 } } }
{ type: 'TextNode',
  value: 'depending',
  position:
   { start: { line: 3, column: 59, offset: 83 },
     end: { line: 3, column: 68, offset: 92 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 68, offset: 92 },
     end: { line: 3, column: 69, offset: 93 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'on', position: [Object] } ],
  position:
   { start: { line: 3, column: 69, offset: 93 },
     end: { line: 3, column: 71, offset: 95 } } }
{ type: 'TextNode',
  value: 'on',
  position:
   { start: { line: 3, column: 69, offset: 93 },
     end: { line: 3, column: 71, offset: 95 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 71, offset: 95 },
     end: { line: 3, column: 72, offset: 96 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'how', position: [Object] } ],
  position:
   { start: { line: 3, column: 72, offset: 96 },
     end: { line: 3, column: 75, offset: 99 } } }
{ type: 'TextNode',
  value: 'how',
  position:
   { start: { line: 3, column: 72, offset: 96 },
     end: { line: 3, column: 75, offset: 99 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 75, offset: 99 },
     end: { line: 3, column: 76, offset: 100 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'fancy', position: [Object] } ],
  position:
   { start: { line: 3, column: 76, offset: 100 },
     end: { line: 3, column: 81, offset: 105 } } }
{ type: 'TextNode',
  value: 'fancy',
  position:
   { start: { line: 3, column: 76, offset: 100 },
     end: { line: 3, column: 81, offset: 105 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 81, offset: 105 },
     end: { line: 3, column: 82, offset: 106 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'you', position: [Object] } ],
  position:
   { start: { line: 3, column: 82, offset: 106 },
     end: { line: 3, column: 85, offset: 109 } } }
{ type: 'TextNode',
  value: 'you',
  position:
   { start: { line: 3, column: 82, offset: 106 },
     end: { line: 3, column: 85, offset: 109 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 85, offset: 109 },
     end: { line: 3, column: 86, offset: 110 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'want', position: [Object] } ],
  position:
   { start: { line: 3, column: 86, offset: 110 },
     end: { line: 3, column: 90, offset: 114 } } }
{ type: 'TextNode',
  value: 'want',
  position:
   { start: { line: 3, column: 86, offset: 110 },
     end: { line: 3, column: 90, offset: 114 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 90, offset: 114 },
     end: { line: 3, column: 91, offset: 115 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'the', position: [Object] } ],
  position:
   { start: { line: 3, column: 91, offset: 115 },
     end: { line: 3, column: 94, offset: 118 } } }
{ type: 'TextNode',
  value: 'the',
  position:
   { start: { line: 3, column: 91, offset: 115 },
     end: { line: 3, column: 94, offset: 118 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 94, offset: 118 },
     end: { line: 3, column: 95, offset: 119 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'final', position: [Object] } ],
  position:
   { start: { line: 3, column: 95, offset: 119 },
     end: { line: 3, column: 100, offset: 124 } } }
{ type: 'TextNode',
  value: 'final',
  position:
   { start: { line: 3, column: 95, offset: 119 },
     end: { line: 3, column: 100, offset: 124 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 100, offset: 124 },
     end: { line: 3, column: 101, offset: 125 } } }
{ type: 'WordNode',
  children:
   [ { type: 'TextNode', value: 'product', position: [Object] } ],
  position:
   { start: { line: 3, column: 101, offset: 125 },
     end: { line: 3, column: 108, offset: 132 } } }
{ type: 'TextNode',
  value: 'product',
  position:
   { start: { line: 3, column: 101, offset: 125 },
     end: { line: 3, column: 108, offset: 132 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 108, offset: 132 },
     end: { line: 3, column: 109, offset: 133 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'to', position: [Object] } ],
  position:
   { start: { line: 3, column: 109, offset: 133 },
     end: { line: 3, column: 111, offset: 135 } } }
{ type: 'TextNode',
  value: 'to',
  position:
   { start: { line: 3, column: 109, offset: 133 },
     end: { line: 3, column: 111, offset: 135 } } }
{ type: 'WhiteSpaceNode',
  value: ' ',
  position:
   { start: { line: 3, column: 111, offset: 135 },
     end: { line: 3, column: 112, offset: 136 } } }
{ type: 'WordNode',
  children: [ { type: 'TextNode', value: 'be', position: [Object] } ],
  position:
   { start: { line: 3, column: 112, offset: 136 },
     end: { line: 3, column: 114, offset: 138 } } }
{ type: 'TextNode',
  value: 'be',
  position:
   { start: { line: 3, column: 112, offset: 136 },
     end: { line: 3, column: 114, offset: 138 } } }
{ type: 'PunctuationNode',
  value: '.',
  position:
   { start: { line: 3, column: 114, offset: 138 },
     end: { line: 3, column: 115, offset: 139 } } }
$ 



Remark(1)何ができる?

about remark/unified framework


2020/08/26
藤田昭人


またまたご無沙汰してしまってますが…
本稿からしばらく、いつもとは趣向を変えて remarkについて集中的に紹介してみたいと思います。

実は本稿はこのブログにポストした30番目の記事になります*1。 で、何よりも問題なのは、過去のブログ記事で何を書いたのか忘れてしまう事です。 最近では「あれ?これ、書いた気がするなぁ」とは思い出すのですが、 肝心の「ブログのどの回で、どのように触れたのか?」を なかなか思い出せないことが増えてます。

こういう時の「書いた気がする」という曖昧な記憶から、 サーチエンジンの検索条件を捻り出すのは案外難しい作業だったりします。 止むなく、過去の記事を順番に流し見てみるのですが…これが結構、イライラさせられます。 そこで「僕の曖昧な記憶に沿った検索」を実現できないか?などと考え始めました。

今もって具体的なアイデアは思い付いてないのですが、 まずは過去のブログ記事を普通に検索できる仕組みを用意することにしました。


Remark: Javascript による Markdown プロセッサ

僕のブログ記事は「はてなブログ」の Markdown で記述しているので、 まずは実績のある Markdown の管理ツール・ライブラリを探してみました。 もちろん Markdown といえば Pandoc があることは承知しているのですが、 前述のとおり、そのうち「曖昧な記憶に沿った検索」を実装したいので、 馴染みのある Javascript ベースの Markdown 管理ツール・ライブラリが欲しいところです。

調べてみたところ Remark なるオープンソースが見つかりました。 次のような 概要 が説明されています。

remark is a Markdown processor powered by plugins part of the unified collective. The project parses and compiles Markdown, and lets programs process Markdown without ever compiling to HTML (it can though). Powered by plugins to do all kinds of things: check Markdown code style, transform safely to React, add a table of contents, or compile to man pages.

remarkは、unified コレクティブのプラグインの一部である Markdown 用のプロセッサです。 このプロジェクトは Markdown をパースしコンパイルして、 HTMLにコンパイルすることなくプログラムがMarkdownを処理することを可能にします。 Markdownのコードスタイルのチェック、React への安全な変換、 目次の追加、あるいはマニュアルページのコンパイルなど、 あらゆる種類の処理を行うためのプラグインを搭載しています。

Pandoc ほどメジャーではなさそうですが、よく似た目標で開発されていて、 もう少しプログラマブルな側面に重点が置かれているような感じです。

実は、当初は甘く見てクイック(ダーティ)ハックで、 ちょこちょこと簡易ツールを作ることを目論んだのですが、 このツールが構築されている Unified なるフレームワークは かなり大掛かりなようで敢なく玉砕。 止むなく正攻法で攻めることにしました。


Remark は何ができるの?

で、チュートリアルを探し回ったのですが…

現時点での Remark の難点の1つはドキュメントの整備が行き届いてないこと。 公式のチュートリアルに相当するのは Using unified という 短めのドキュメント(メモ書きとも言う)あたりになるようです*2

ここでは、このメモ書きをさらに端折って紹介します。


基本的な Markdown から HTML への変換

まず、もっとも基本的な Markdown to HTML のコンバーター index.js は次のとおりです。

var unified = require('unified')
var stream = require('unified-stream')
var markdown = require('remark-parse')
var remark2rehype = require('remark-rehype')
var html = require('rehype-stringify')

var processor = unified().use(markdown).use(remark2rehype).use(html)

process.stdin.pipe(stream(processor)).pipe(process.stdout)

JavascriptPromise を使って、 Unix のパイプのような記法で処理が記述できるのが(僕には)わかり易い。 このコンバーターUnix のフィルターのような使い方をするようです。

node index.js < example.md > example.html

ちなみに次のようなファイル example.md を入力すると…

# Hello World

## Table of Content

## Install

A **example**.

## Use

More `text`.

## License

MIT

次のようなファイル example.html が出力されます。

<h1>Hello World</h1>
<h2>Table of Content</h2>
<h2>Install</h2>
<p>A <strong>example</strong>.</p>
<h2>Use</h2>
<p>More <code>text</code>.</p>
<h2>License</h2>
<p>MIT</p>


もう少しリッチな HTML への変換

さらにプラグインを使うように index.js を修正すると…

--- ../Tree transformations/index.js 2020-08-20 13:52:22.000000000 +0900
+++ index.js  2020-08-20 14:11:07.000000000 +0900
@@ -1,9 +1,18 @@
 var unified = require('unified')
 var stream = require('unified-stream')
 var markdown = require('remark-parse')
+var slug = require('remark-slug')
+var toc = require('remark-toc')
 var remark2rehype = require('remark-rehype')
+var doc = require('rehype-document')
 var html = require('rehype-stringify')
 
-var processor = unified().use(markdown).use(remark2rehype).use(html)
+var processor = unified()
+  .use(markdown)
+  .use(slug)
+  .use(toc)
+  .use(remark2rehype)
+  .use(doc, {title: 'Contents'})
+  .use(html)
 
 process.stdin.pipe(stream(processor)).pipe(process.stdout)

出力される example.html は次のように変わります。

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Contents</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1 id="hello-world">Hello World</h1>
<h2 id="table-of-content">Table of Content</h2>
<ul>
<li><a href="#install">Install</a></li>
<li><a href="#use">Use</a></li>
<li><a href="#license">License</a></li>
</ul>
<h2 id="install">Install</h2>
<p>A <strong>example</strong>.</p>
<h2 id="use">Use</h2>
<p>More <code>text</code>.</p>
<h2 id="license">License</h2>
<p>MIT</p>
</body>
</html>


ファイル名を明示的に与える場合

さらにファイル名を明示的に渡すには…

--- ../Plugins/index.js  2020-08-20 14:11:07.000000000 +0900
+++ index.js  2020-08-20 14:31:16.000000000 +0900
@@ -1,5 +1,6 @@
 var unified = require('unified')
-var stream = require('unified-stream')
+var vfile = require('to-vfile')
+var report = require('vfile-reporter')
 var markdown = require('remark-parse')
 var slug = require('remark-slug')
 var toc = require('remark-toc')
@@ -15,4 +16,9 @@
   .use(doc, {title: 'Contents'})
   .use(html)
 
-process.stdin.pipe(stream(processor)).pipe(process.stdout)
+processor.process(vfile.readSync('example.md'), function (error, file) {
+  if (error) throw error
+  console.error(report(file))
+  file.extname = '.html'
+  vfile.writeSync(file)
+})

…と修正します。 この例では入出力ファイルをソースにハードコードしているので…

$ node index.js
example.md: no issues found

となります。


Markdown のテキスト部分を取り出してチェック

最後に僕的には興味深い機能ですが…
Markdown ファイルのテキスト部分だけを取り出して 構文チェックをする例も紹介されてます。

--- ../Reporting/index.js    2020-08-20 14:31:16.000000000 +0900
+++ index.js  2020-08-20 15:45:39.000000000 +0900
@@ -4,12 +4,16 @@
 var markdown = require('remark-parse')
 var slug = require('remark-slug')
 var toc = require('remark-toc')
+var remark2retext = require('remark-retext')
+var english = require('retext-english')
+var indefiniteArticle = require('retext-indefinite-article')
 var remark2rehype = require('remark-rehype')
 var doc = require('rehype-document')
 var html = require('rehype-stringify')
 
 var processor = unified()
   .use(markdown)
+  .use(remark2retext, unified().use(english).use(indefiniteArticle))
   .use(slug)
   .use(toc)
   .use(remark2rehype)

…と修正して、実行すると…

$ node index.js
example.md
  7:1-7:2  warning  Use `An` before `example`, not `A`  retext-indefinite-article  retext-indefinite-article

⚠ 1 warning

…と「example の冠詞は An ですよ」とワーニングが表示されます。


まとめ

…とまぁ、本稿では Remark のチュートリアルにあるサンプルを紹介しました。

どうやら Remark は JavascriptPromise を活用して、 Markdown ドキュメントを対象に(Unixのパイプのように) 様々な機能ブロックを継ぎ足して処理を行うツールのようです*3

次回は機能ブロックを実現している Plugin について調べてみたいと思います。

以上

*1:2019年2月から1年半の間に30本ですので、 月刊連載の頃に比べれば大幅にペースアップしてますが、 当初の目標だった月2本のペースには届いていません。 ちなみに『Unix考古学』は連載26ヶ月分だったので、 分量的には1冊分は書いてることになるのですが、 今の草稿は各回の内容が散漫過ぎるので これをベースに直ちに単行本を執筆するのはなかなか厳しそうです。

*2:"Unified Handbook" なるドキュメントも見つけましたが…

github.com

これまた "This is a work in progress" なんだそうです。

*3:このビルディング・ブロックを継ぎ足す機能は Unified フレームワークが 提供しているのでしょうが…

アラン・チューリングの戦時の最大の成果は何だったのか?

What was Alan Turing's greatest wartime achievement?


2020/08/05
藤田昭人


このところアラン・チューリングに関する話題に執着してますけども…

チューリングのネタもまたまた底無し沼でして、 「本稿で一旦区切りを付けよう」と考えて執筆に取り掛かったのですが、 まとめられずにボヤボヤしている間に1ヶ月経ってしまった と言ったところです。「かくなる上は…」と力づくで書いてるので 今ひとつの内容かもしれませんが、よろしくお付き合いください。

さて…

前回 はNHKの人気ドキュメンタリーシリーズ「フランケンシュタインの誘惑」の アラン・チューリングの回にコメントする形で記事を書きました。 番組のトーク・パートの中で「1939年から1945年、チューリングが27歳から 33歳の研究者として最も生産性の高い年齢の期間の業績を封印されるのは、 研究者個人としては致命的な打撃を受ける」との発言がありました。 でもその研究成果については…番組では暗号解読機 bombe の紹介ばかりで、 それ以上掘り下げられることはありませんでした。 本稿では、そこを掘り下げたいと思います。


アラン・チューリング・イヤー

話が少し飛んでしまいますが…

アラン・チューリングの生誕100周年だった2012年は アラン・チューリング・イヤー とされ、年間を通じてチューリングの業績を讃える様々なイベントが世界中で開催されました。 その最中、チューリングの誕生日の2ヶ月前の2012年4月、BBCは次のニュースを報道しました。

www.bbc.com

"Two 70-year-old papers by Alan Turing on the theory of code breaking have been released by the government's communications headquarters, GCHQ."

アラン・チューリングによる暗号解読理論に関する70年前の論文2本が、政府通信本部GCHQ)によって公開されました」

という地味な表現で始まるこの報道は、チューリングの様々な革新的な研究のうち 最後まで隠され続けた成果がとうとう公開されたことを報じたものです。 これ、もし70年前に明らかになっておればその後の世界に大きな影響を与えたかもしれない。 ひょっとしたらチューリング最大の研究成果だったのかもしれません。 なので、単に「暗号解読理論」(the theory of code breaking)などと 素っ気なく表現するあたりのマスコミのセンスはやはり英国ならでは?と思ってしまいます(笑)


エニグマの暗号解読について

今日ではチューリングの貢献がクローズアップされるエニグマの暗号解読ですが、 実はチューリングが手掛けるずーっと以前、1920年代からイギリスだけでなく フランスやポーランドでもエニグマの暗号解読は試みられてきました。 特にドイツと陸続きで隣接するフランスやポーランドにとって、 ドイツの軍用暗号は自国の死活に直結する問題でしたので多くの労力を注ぎ込んでいた事は想像に難くありません。 1932年、ポーランドの数学者であるマリアン・レイェフスキがエニグマの暗号解読に成功しました。

ja.wikipedia.org

レイェフスキはエニグマインジケーター を利用して暗号解読を行いました。エニグマを使って暗号文での通信を行うには 伝文ごとに送信側と受信側のエニグマのローターやプラグの設定を合わせる必要がありましたが、 その設定を示すのがインジケーター、ちょうど暗号メールメッセージに付与するパスワードみたいなものです。 レイェフスキは今日のパスワード・ハックとよく似た手法を使ってインジケーターを推測し、 暗号文の解読に成功したようです。しかし、この方法の弱点はドイツ軍がインジケーターの決定ルールを変更する度に、 解読方法をゼロから調べなければならないことで、それが「不安定な解読方法」と呼ばれた由縁のようです。

このようにエニグマの暗号解読には成功していたポーランドですが、 1939年9月にドイツとソ連に挟撃される形で一気に占領されてしまいました。 これが第2次世界大戦の始まりです。戦争が不回避な状況を事前に察知していたため、 1939年7月、ポーランドの暗号局はエニグマの暗号解読に関わる一切の情報を イギリスとフランスに対し開示しました。その中にはポーランドの手で復元した エニグマのクローン機 2台も含まれました*1。 ドイツとソ連によるポーランド占領後、レイェフスキたち暗号局の面々はフランスの ブルーノ暗号機関 に移って暗号解読の作業を続けました。 これはドイツがフランスに侵攻する1940年6月まで続けられました。

チューリングが政府暗号学校(GC&CS)に着任したのは、 ポーランド暗号局の情報開示が行われた時期と概ね重なります。 フランスの諜報機関がスパイ活動などで集めたエニグマに関する情報の断片、 さらにポーランド暗号局による解析結果などなど、 イギリスはこの時、これらの膨大は情報をまとめて手にすることができました。 いよいよ天才の出番でした。

インジケーターに大きく依存するレイェフスキの解読方法に懸念を持っていた イギリスの暗号解読のスタッフは、それとは異なるアプローチを採用しました。 今日 Crib-based decryptionクリブ 式暗号解読)と呼ばれる手法です。「クリブ」とは 「既知のもしくは想定されうる平文のサンプル」を意味する ブレッチリー・パークで使われた隠語だったそうです。 暗号化されたメッセージ本文に暗号文とそれに対応するクリブがわかっている断片を 付き合わせることで解読する、膨大な作業量を必要とするアプローチでした*2

このクリブを使って、エニグマメッセージに使用される可能性のある正しい設定 (すなわち、ローターの順番、ローターの設定、プラグボードの設定)を 機械的に探索するために作られたのが bombe でした。この機械はエニグマと等価な動作をするホイールを36個使って、 総当たり式に正しい設定を探ります。ホィールを3個1組で使う手法は、 レイェフスキが同じ目的で製作した bomba から拝借したアイデアです。 また、チューリングGC&CS に所属しているときにも渡米してますが、 その際にアメリカで開発されていた bombe を見て、次のように書き残しています。

The American Bombe programme was to produce 336 Bombes, one for each wheel order. I used to smile inwardly at the conception of Bombe hut routine implied by this programme, but thought that no particular purpose would be served by pointing out that we would not really use them in that way.

アメリカの bombe 計画は336個の bombe を生産することになっていた。 私はこのプログラムが暗示するbombeの hut ルーチンに内心微笑んでいたが、 目的は果たせないだろうと思いながら、 実際にはそういう使い方はしないだろうと指摘した。

この「全くわかっちゃいない」的なコメントもまた、 第2次世界大戦当時のイギリスとアメリカの技術格差を物語るエピソードでしょう*3

ちなみにWikipedia英語版には以下の「エニグマの暗号解読」というページが存在します。

en.wikipedia.org

エニグマについて、歴史的な事実を踏まえ、 技術的にどのような方法が使われたのかわかりやすく解説している、 初心者向けの暗号解読マニュアルのような内容です。 エニグマに関心のある高校生や大学生の皆さんにはお勧めのテキストでしょう。


チューリングが政府暗号学校(GC&CS)で挙げた5つの研究成果

アラン・チューリングといえば、一般には bombe の開発で知られているのではないでしょうか? 特徴的なホィールが無数に並ぶ筐体はやはり一般にも「絵になる」わかり易い成果ですから。

もっとも Wikipedia の「アラン・チューリング」のページの 「暗号解読」 の項によると、bombe はチューリングGC&CSで挙げた5つ研究成果のうちの ひとつ目なんだそうです。5つ研究成果を次にあげておきます。

  • ボンベ(Bombe) の設計
  • ドイツ海軍が使用するインジケーターの手順を推定する
  • バンベリスムス(Banburismus)の考案
  • チューリンジャー(Turingery)の考案
  • デリラ(Delilah)の設計

このうち最も注目すべきはバンベリスムスです。 Bombeでの暗号解析時間を圧縮するため、1940年ごろに考案されたこの解析手順は、 条件付き確率による逐次分析を使用して、最短でエニグマの正しい設定にたどり着く推測します。 分析手法以外で特筆すべき事柄として、推測の正しさを表す単位としてバン(ban)と呼ばれる 独自の指標を発明したことです。この単位は クロード・シャノン が1948年に発表した情報理論で定義した情報量の概念を先取りした概念だそうです。 また、チューリンジャーはバンベリスムスで確立した手法を応用して、 エニグマよりも暗号強度の高いローレンツ暗号を解読する手順です。


バンベリスムスとベイズ統計学

Wikipedia の「アラン・チューリング」のページでは、 バンベリスムス(およびチューリンジャー)について次のように紹介しています。

By using statistical techniques to optimise the trial of different possibilities in the code breaking process, Turing made an innovative contribution to the subject. He wrote two papers discussing mathematical approaches, titled The Applications of Probability to Cryptography[64] and Paper on Statistics of Repetitions,[65] which were of such value to GC&CS and its successor GCHQ that they were not released to the UK National Archives until April 2012, shortly before the centenary of his birth. A GCHQ mathematician, "who identified himself only as Richard," said at the time that the fact that the contents had been restricted for some 70 years demonstrated their importance, and their relevance to post-war cryptanalysis:[66]

統計的手法を用いて、コード解読手順における様々な可能性の試行を最適化することによって、チューリングはこの主題に革新的な貢献をした。彼は、数学的アプローチを論じた2つの論文「The Applications of Probability to Cryptography」(暗号への確率論の応用)と「Paper on Statistics of Repetions」(反復統計に関する論文) を執筆した。これらの論文はGC&CSとその後継者であるGCHQにとって価値があり、彼の生誕100周年の直前である2012年4月まで英国国立公文書館に公表されなかった。当時、GCHQの数学者は「彼は自分をリチャードとしか名乗らない」と語ったが、この内容が約70年間制限されていたという事実が、その重要性と戦後の暗号解読との関連性を示している。

冒頭で語ったチューリングの「最後まで隠された研究成果」とは、問題解決の方法として統計学的手法を活用することを語った論文でした。 すなわち、今日で言うデータ・サイエンス的な手法を使って、チューリングは80年前にドイツ軍の暗号解読に成功していたことになります。

ちなみに、この論文2篇はPDF化されたバージョンが arxiv からダウンロードできます。

arxiv.org

arxiv.org

では、チューリングが用いたされる統計的手法とはどんなものだったのでしょうか? 統計学の専門家の間ではチューリングの統計的手法は「ベイズ統計学」に分類されるとみなされているようです。 書籍『異端の統計学 ベイズ』 では、18世期に生まれた「ベイズの法則」がその後たどってきた歴史を紹介しています。

www.soshisha.com

同書は全編を通して、頻度主義者を相手に苦しい闘いを続けてきたベイズ学派の長い苦闘の歴史が語られています。 「客観性に欠けるが実用では役に立つ」ベイズ統計学の華々しい成果のひとつとして、 チューリングの暗号解読について第4章を丸々使って紹介していますが、 今日のベイズの隆盛の遠因となったパーソナルコンピュータの普及にチューニングが果たした役割や、 おそらく深いところでは密接に関連しているだろう人工知能とチューニングの関わりについて、 記述が見つけられないのは「これが統計学者の史観なんだろうなぁ…」と思ったりします。

書籍『異端の統計学 ベイズ』が語るチューリングの最後もまた悲劇的なのですが、 類書と幾分違うのは「チューリングが編み出した手法は暗号学の中で生き残った」としているところです。 もっとも、それがベイズ学派の苦闘に利することもなかったとしてます。 同書によればベイズ統計学の「決定的なブレークスルー」が登場するのは1989年あたり。 冷戦の終結した時期と重なります。これは単なる偶然ではないのかもしれませんね。


戦時中の研究成果についてチューリング自身はどのように考えていたのか?

ナチス・ドイツが降伏した途端、 ウィンストン・チャーチルGC&CSの研究成果を封印するように命じたそうですが、 この命令がコンピュータ・サイエンスの世界ではとんでもない機会損失となったことは間違いないでしょう。 結局、チューニングの成果は長いあいだ軍事技術の世界に独占されることになりました。 その長らく封印されていた最後の秘密が公開されるまでに70年間を要したという、 ある意味では大変気の長い話ではあります。

冒頭で紹介したBBCのニュース報道は次のような文章で終わります。

According to the GCHQ mathematician, who identified himself only as Richard, the papers detailed using "mathematical analysis to try and determine which are the more likely settings so that they can be tried as quickly as possible."

...

Richard said that GCHQ had now "squeezed the juice" out of the two papers and was "happy for them to be released into the public domain".

He added that the work of Bletchley Park was held in high regard by GCHQ. "I think we are very proud of the history of our organisation and like to think that we are their successors," he said.

GCHQの数学者で、自らをリチャードとだけ名乗った人物(チューリングのことのようです)によると、 論文の詳細は「可能な限り迅速に試すことができるように、 どちらがより可能性の高い設定であるかを決定しようとする数学的分析」 を使って書かれているという。

...

リチャードは、GCHQは現在、2つの論文から「ジュースを絞り出した」と述べ、 「パブリックドメインとしてリリースされることに満足している」と語った。

彼は「ブレッチリー・パークの仕事はGCHQによって高く評価されている」と付け加えた。 「私達は、私達の組織の歴史を非常に誇りに思っているし、私達が彼らの後継者だと思いたいと思っている」と彼は語っている。

この最後の部分をどのように解釈すべきかは悩ましい問題です。

というのも、1912年生まれのチューリングは、 中央集権国家による帝国主義の時代であった 19世期の価値観に影響を受け、大きく引きずっていたように想像するからです。 おそらく当時は国家への貢献を誇らしいと考える人は今よりずっと多かったでしょう。 第2次世界大戦中の軍事研究の当事者の行いについて 後世の価値観でもって断罪する輩は多いのですが、 彼らが生きた時代の社会通念を考慮しないのはフェアではないように僕は思うからです。 こういった歴史上の事実について、現在の科学者が「僕ならこのような研究には加担しない。 もし逃れようがなければサボタージュをする」などと答える例がありますが、 これは問題の本質を取り違えた短絡的で無責任な発言だと言わざる得ません。

実際、コロナ禍の真っ只中にある現在においては特にリアルな問題でしょう。 現在、医学や生理学の研究者はコロナ・ウィルスの正体を見極め、 その治療法を確立しようと賢明の努力をされていることでしょう。 人間の生死に関わる問題だけに時間との勝負になってるのだと思いますが、 故に多くの研究者が自らの研究成果を論文にまとめたら、 すぐにインターネットで公開して情報共有しておられる。 これは同時に事実上自らの研究成果をパブリックドメインとしていることになります。 研究者一個人の視点で見れば、これは戦時中のアラン・チューリングの立場と全く変わらないように思うのです。 個人にとって「起こってしまった戦争」は「突如発生したパンデミック」と何ら変わらない。 その渦中にあっては、誰もが問題解決のために自分に出来ることを努力するしかないのです。

そのように考えると、記事が語る「パブリックドメインとしてリリースされることに満足している」 とのセリフの背景には様々な思いが横たわっているように感じてしまうのです。

もっとも…

そこは浮世離れした変人としても知られていたチューリングのことですから、 このように世俗にまみれた事柄に頭を悩ますようなことは案外なかったのかもしれません。

著名なチューリング研究家であるアンドリュー・ホッジス(Andrew Hodges)によれば、 チューリング個人にとってブレッチリー・パークでの経験は、 当時の最先端の電子工学を学ぶ機会でもあったそうです。 彼は回路設計の方法を独学で習得したそうですが、 それは彼にとって、ケンブリッジ大学の時代には 夢想するしかなかったユニバーサル・マシンを 実現する方法を習得することに他ならなかったようです。

では、「万能機械」を更に発展させた「知能機械」を チューリングが構想するようになったのは何時ごろから何でしょうか?ホッジスは 論文 "Alan Turing and the Turing Test" の中で次のように語っています。

But in the period around 1941 when the immediate crisis of the Enigma problem was resolved, Turing began to discuss with colleagues at Bletchley Park the possibility of machines playing chess (Hodges, 1983). Chess-playing was in fact a civilian analogue of what they were doing in their secret military work, in which mechanical methods of great sophistication were outdoing some aspects of human intuition. It seems, taking the view as expressed in Hodges (1997, 2002), that Turing probably decided in about 1941 that the scope of computable operations was in fact sufficient to account for those mental operations apparently "non-mechanical" by the standards of ordinary language, and even the apparently uncomputable operations of truth recognition.

しかし、エニグマの問題の差し迫った危機が解決された1941年頃、チューリングはブレッチリー・パークで同僚とチェスをする機械(ホッジス、1983)の可能性について議論を始めた。チェスのプレイは、実は彼らが手掛けていた秘密の軍務の非軍事的な類似作業であり、高度に洗練された機械的手法が人間の直感の一部を凌駕していた。ホッジズ(1997、2002)が示した見解を考慮すると、どうやらチューリングはおそらく1941年頃、計算可能な操作の範囲でも通常の言語の基準では明らかに「機械的でない」と思われる精神的な操作を説明するのに実際には十分であると決めたのだろう。

あるいは、バンベリスムスが弾き出す「機械的でない」推測に 知性の片鱗を感じ取ったチューリングは その直感を検証するために「チェスをする機械」を構想し始めたのかもしれないですね。 そう考えると、後世の人間の憶測とは裏腹に、 チューリング個人にとってブレッチリー・パークでの日々は 彼自身のその後の未来に繋がる幾つかの確信を得られた、 案外実りの多い時間だったのかもかもしれません。

以上

*1:エニグマは元々商用で 一般に販売されているモデルもありましたし、 携帯できる野戦用の暗号機でもありましたので、 破棄された機器をかき集めると復元できたのでしょう。 これもポーランド暗号局の地道な努力の成果です。

*2:日本人である僕がこの話から連想するのは江戸時代の 杉田玄白 の 『解体新書』 の翻訳です。今日であれば Google が開発した Seq2Seq 基本的には同じアイデアなんだと思います。

*3:もちろん「チューリングが天才だったから」という反論もあるでしょうが、 同じような両国間でのエンジニアの基本的技術素養のギャップは、 原爆開発プロジェクトだった マンハッタン計画 でも、それは浮き彫りにされたと言われています。

例えば、長崎に投下されたプルトニウム型原爆に使われた 爆縮レンズ は長らくトップシークレットとされてきました。今日でもこの開発については ジョン・フォン・ノイマン の名前が語られることが多いのですが、 実はノイマンが手掛けたのは火薬の燃焼速度の理論計算だけ。 爆縮レンズの基本的なアイデアである燃焼速度の違う複数の火薬を使う手法を 最初に考えたのはドイツ人の理論物理学者である クラウス・フックス でした。

マンハッタン計画に参加したとあるアメリカ人科学者は後日 「プロジェクトでのアメリカ人科学者は大学を出たばかりの青二才といった様子だったが、 ヨーロッパからやってきた学者は正しく原子核研究のプロの研究者と呼べる人達で プロジェクトはたちまち彼らに主導されることとなった」と語ったとか。

実際、アメリカにとって第2次世界大戦は、 当時の最先端技術を効率よく習得する機会でもあったことは否めません。 第2次大戦後、プロ研究者たちは国力低下により母国での職を失い、 アメリカの誘いに乗って大西洋へ渡る人たちが多数いたそうです。 彼らが現在のアメリカの技術優位に大きく貢献したことは間違いなさそうです。

この当時のヨーロッパ諸国とアメリカとの先端技術をめぐる関係は、 現在のアメリカと中国とのテクノロジーに関する関係を彷彿させますね。

アラン・チューリングは「人工知能を予言した男」だったのか?

Is Alan Turing "the man who predicted artificial intelligence"?


(最終)2020/07/03
(追記)2020/06/26
(追記)2020/06/24
2020/07/03
藤田昭人


遅くなってしまいましたが…
(再放送の)再放送が終わったのでブログを書きます。

www.nhk.jp

科学史の闇に埋もれた事件に光を当て、科学の正体に迫るドキュメンタリー」 と銘打って加害者となった科学者に焦点をあてるこの番組シリーズの中では、 2017年7月27日に初めて放送されたこの「CASE15 強制終了 人工知能を予言した男」は 異色の番組だったと思います。というのも主人公である アラン・チューリング は、加害者などではなくむしろ被害者と言うべきだからです。 いつもとは勝手が違ったためなのか、それとも「 電脳戦 」や「 シンギュラリティ 」、あるいは人工知能学会の「 倫理指針 」といった2017年当時の世相に引きづられ過ぎたためか、 この番組シリーズの他の CASE よりも切れ味が鈍いとの印象を僕は持ちました。

この回では、アラン・チューリング研究では世界的にも著名な アンドリュー・ホッジズジャック・コープランド の二人へのインタービューを行っており 内容は技術的・歴史的な考証は概ね正確だったと思います。 ですが、特にトークパートでは1980年代あたりによく語られた チューリング史の議論が繰り返されたように感じたので、少なからずガッカリしてます。

以下は僕がイラッと来たポイントを簡単に述べます。


チューリングは没後忘れ去られていたのか?

番組では「チューリングは没後は忘れ去られていた」かのように語られてました。 が、少なくともコンピュータ・サイエンスの世界では チューリングの研究業績が忘れられた事は片時もなかったと僕は思います。

その典型的な事例があります。アメリカのコンピュータ・サイエンスの学会である Association for Computing Machinery (ACM) は1966年にチューリングの業績を称え、彼の名前を冠した チューリング賞 を創設しています。今日、情報の世界で最も権威のあるこの賞は「コンピュータ・サイエンスのノーベル賞」と言われています。 番組では、第2次世界大戦後も隠蔽された エニグマ の暗号解読について、初めて暴露した F.W.ウィンターボーザム の書籍 『The Ultra Secret』 の出版が転機のように紹介されましたが、この書籍が出版されたのは1974年ですので、 この書籍が登場するまでチューリングが忘れ去られた存在だったと言うのは誤りです。

確かに1950〜1970年代にはコンピュータサイエンスの専門家の間でチューリングが提案した プログラム内蔵方式のコンピュータ の起源についてあまり語られなかった、 というよりもどちらかと言えばそれは議論が避けられたトピックであったことも事実です。 その大きな理由は 「ENIAC特許に関する訴訟」 *1 の係争が続いていたからだと僕は思います。 この時期にチューリングの主要な業績である 「プログラム内蔵方式のコンピュータ」を語ることを自粛せざる得ない やむ得ない現実が当時はあったのです。


通説である「チューリングの悲劇」はどこで生まれたのか?

番組は一貫してチューリングの物語を悲劇として描き出していますが、 それはアンドリュー・ホッジズが1983年に出版したチューリングの伝記 "Alan Turing: The Enigma" と、それを原作として1986年に初演された ヒュー・ホワイトモア の戯曲 ブレイキング・ザ・コード が下敷きになっています。この戯曲の 脚本 は現在でも入手可能なのですが、次のように紹介されています。

This compassionate play is the story of Alan Turing, mathematician and father of computer science. Turing broke the code in two ways: he cracked the German Enigma code during World War II (for which he was decorated by Churchill) and also shattered the English code of sexual discretion with his homosexuality (for which he was arrested on a charge of gross indecency). Whitemore's play, shifting back and forth in time, seeks to find a connection between the two events. When first performed in the 1980s, Breaking the Code was critically acclaimed in the UK before a Broadway transfer won it a raft of awards & nominations including 3 Tony Awards, and 2 Drama Desk awards.

この思いやりのある芝居は、数学者でありコンピュータ科学の父であるアラン・チューリングの物語である。チューリングは、第二次世界大戦中にドイツのエニグマの暗号を解読し(そのためにチャーチルから勲章を授与された)、また、同性愛のために英国の性の規範を打ち砕いた(そのために彼は重大なわいせつ行為の罪で逮捕された)。ホワイトモアの戯曲は、時間を行き来しながら、この2つの出来事の間に関連性を見出そうとしている。1980年代に初演された『ブレイキング・ザ・コード』はイギリスで絶賛された後、ブロードウェイに移籍し、トニー賞3部門、ドラマデスク賞2部門を含む数々の賞とノミネートを獲得しました。

この戯曲は世界中でヒットしたようです。 いかがでしょう?この「天才科学者の秘められた生活」といったプロット、 よく考えてみれば如何にも演劇的ですよね? 以降、戯曲で作られた「暗号解読」と「同性愛者」という チューリングのエキセントリックなイメージが一般に定着しました。

これが結果的には、今日でもますます盛んなアラン・チューリング研究の発端だったと僕は理解しています。 もちろん、彼のプライベートを暴くことの是非はありますが、伝記や戯曲の登場により 歴史の闇に埋もれていったその他の幾多の天才とは異なり チューリングへの社会的な認知が広がり、 その後の再評価へと繋がったことは明らかでしょう。

これが冒頭で言及した「1980年代のチューリング史」です。
しかし、今となっては「前世紀の逸話」と言うべきなのかもしれません。


今、本当に語られるべき「チューリングのストーリー」は何か?

今世紀に入り、英国政府からの謝罪により名誉回復され、 祖国を守った英雄のひとりとなったアラン・チューリング。 「今世紀のチューリング史」を語るとしたら、 その後のチューリングの再評価の歴史にも触れなければ片手落ちでしょう。

まず軍関係では、英国情報局は書籍『The Ultra Secret』の出版を皮切りに、 第2次世界大戦中の作戦に関するすべての情報の軍事機密の解除に応じるようになりました。 また、チューリングが戦時中に所属していた 政府暗号学校 (GCCS) (現 政府通信本部(GCHQ)) があった ブレッチリー・パーク は一時期廃墟のような状態になっていましたが、 その後その歴史的価値を評価した地方自治体によって保全され、 1994年にブレッチリー・パーク博物館として再建されました。 今日では年間20万人の来場者数を誇る人気スポットとなっています。

今世紀に入って、チューリングは「性的マイノリティ(LGBT)」問題の象徴的な存在にもなりました。 2009年に当時のゴードン・ブラウン首相が、政府がかつておこなった「非人道的な扱い」を謝罪しました。 2013年には、エリザベス女王が死後恩赦を与え、 さらに2016年には過去に同性愛で有罪となった故人数千人も自動的に赦免されることになりました。 この措置は「アラン・チューリング法」と呼ばれています*2。 今日では「コードブレーカー」の異名で知られるチューリングですが、 それは「暗号解読」という意味に加え「(悪しき)行動規範を打ち破る」という ダブル・ミーニングとして語られることもあるようです。

このように、没後はむしろ社会的な影響力を増したチューリングですが、 残念なことに番組ではシリーズ全体のコンセプトとの整合性を守るためか、 チューリング存命中の悲劇的なストーリーだけを切り取った形で構成されていましたが、 放送の前年に成立した「アラン・チューリング法」にさえ全く触れていなかったことにっと思ってしまいました。


チューリングは「人工知能預言者」だったのか?

番組で僕が一番イラッとしたのは『人工知能の預言した男』という CASE タイトルです。 これ「チューリング預言者だ」としたこと、 それから「チューリングの研究は人工知能を先取りしたものだった」 としたことに強い違和感を感じたからです。


チューリング預言者とは対照的な人物

そもそも、 数学者だったチューリングの仕事は学術論文や提案書を書くことでした。 番組でも紹介された、 チューリング・マシン を提案した論文 "On Computable Numbers, with an Application to the Entscheidungsproblem" (計算可能数について - 決定問題への応用)(1936)*3 や世界初のプログラム内蔵型コンピュータになるはずだった "Automatic Computing Engine (ACE)" の開発に関する提案書 "Proposed electronic calculator"(1945)*4 など、短い生涯の間に重要な論文を多数残しています。 特に彼の論文の特徴は具体的で詳細まで丁寧に説明されていることでした。例えば、 チューリング・テスト (これも番組に登場しましたよね?)について語った論文 "Computing Machinery and Intelligence"(計算する機械と知性について)(1950)*5 については福井県立大学の田中求之先生が読み易い和訳を公開されてますので、 チューリングの論文の特徴を実感してもらえると思います。

mtlab.ecn.fpu.ac.jp

いかがでしょう。その書きぶりの丁寧さは中学生・高校生向けの数学の副読本を思わせますよね?*6 チューリングの論文が具体的で細部まで丁寧に書かれていたのは、 周囲の人間に自分のアイデアを正確に理解させるためだったと僕は思います。 数学者だったチューリングがコンピュータを製造するには(ハードウェア)エンジニアが必要でしたし、 そのための資金を得るためには上司にアイデアを理解させ、説得する必要がありました。 そこには一般的なイメージとは異なる「全てを自分の思い通りに進める」というチューリングの執念が感じられます。 こういった「強烈な自我のある孤高の天才」は「社交性はあるがどこか他人頼りな預言者」とは 対極に位置すると思います。


本当の「人工知能を予言した男」とは?

次は「人工知能(AI)」について。 この用語の由来については 以前ブログに書いた ことがあるのですが、その内容をザックリ説明すると…

このダートマス会議の50周年を記念して2006年に開催されたイベントで、 マッカーシーが語った会議の内幕は…

  • 会議での議論そのものは期待外れに終わった
  • その主な理由は参加者がそれそれ自分自身の研究テーマに固執し、他者の研究テーマに関心を示さなかったから
  • もしチューリングが存命で、会議に参加していれば、全く違った結果になったであろう
  • 会議での最大の成果は "Artificial Intelligence" という用語が研究者の間で定着したこと

ということだったようです。その後、AIは研究者の間でのブームとなりました。 ミンスキーが1961年に発表した論文 "Steps Toward Artificial Intelligence"(1961) *7ダートマス会議から5年後の人工知能研究の隆盛ぶりを報告する内容でした。 一説によれば、このときミンスキーは「向こう30年以内に人工知能は実用化される」と豪語したとか。

しかし、1990年を過ぎてもミンスキーが認める人工知能が実用化されることはありませんでした。 もちろんミンスキーは「研究は前進している」と主張していましたが…

マッカーシーは2011年に、ミンスキーは2016年に亡くなりました。 結局、彼らが夢見た「人工知能」を彼ら自身は生涯、目にすることはありませんでした。 もし「人工知能を予言した男」が実在したとすれば、 それは彼らだったのではないかと…いかがでしょうか?


「20世期のAI」と「21世紀のAI」

現在、AIの技術的主流とされている「ディープラーニング」は 旧来の「機械学習」と「ニューラル・ネットワーク」という技術を組み合わせたものなんだそうですが、 マッカーシーミンスキーが全盛を極めた1960年代から1980年代あたりまでは、 いずれも人工知能の範疇には含まれない技術とみなされていました*8マッカーシーミンスキーが全盛の時代、 日本では「記号処理」=「人工知能」と考えられていたのですが、 今では「コネクショニズム」=「人工知能」と考えることが多くなっています。 このブログでは「記号処理」を「20世期のAI」、 「コネクショニズム」を「21世期のAI」と呼ぶことにしていますが、 二つの世紀の間に位置する1990年〜2010年の間に、 研究のトレンドが「記号処理」から「コネクショニズム」へと移行する動きがありました。

番組では「我々が求めているのは経験から学習する機械だ」 との派手なキャプションで語っていましたが、 チューリンがこの発言をしたのは「1947年ロンドン数学会での講演」*9 で、 その翌1948年には"Intelligent Machinery"(知能機械)*10 というタイトルの報告書を作成しています。両者で語られているのは「経験から学習する機械」である "Unorganized machine"(未構成のマシン) ついてです。つまりチューリングは「コネクショニズム」のアプローチで 機械の知能獲得を考えていたことになります。 この講演録と報告書、実は長年埋もれていたそうです。 番組でインタビューに応じていたジャック・コープランドは サイエンティフィック・アメリカン誌に "Alan Turing's Forgotten Ideas in Computer Science"(1999)*11 を寄稿し「経験から学習する機械」の発掘に一役買っています。

このように1990年代は、既存の「記号処理」アプローチがデッドエンドを迎え、 その打開策を「コネクショニズム」に求めるトレンドもあったのでしょう。その中にあって 「チューリングニューラルネットワークを使ったコンピュータを構想していた」 という新事実は、かなりのインパクトだったことは想像に難くないでしょう。

もちろん「20世期のAI」から「21世期のAI」への パラダイムシフトの主な要因は21世期に入ってからのディープラーニングのブレーク、 その背景にはクラウド・コンピューティングの出現があってのことなのですが、 チューリングの「経験から学習する」コンセプトが従来の「人工知能」の諸問題に 問題解決のための新しい切り口を示唆した面も考えられるように思います。 でなければ、世紀の変わり目にチューリング再評価の機運が高まることはなかったはずだから。

ちなみに晩年のミンスキーはかなり苛立っていたように見えます。 雑誌 Wired の2003 年のインタビューに答えて「1970年以降のAI研究は脳死状態にある」と語っています*12。 つまり21世紀には、彼らの「人工知能」は前世紀ほど 注目を集められなくなってしまったことを物語っています。


結局、アラン・チューリングとは?

以上が『フランケンシュタインの誘惑』の「CASE15 強制終了 人工知能を予言した男」の再放送を見た僕の感想です。

実は、この回は初出の 2017年の放送の時も視聴した筈だったのですが、 その時の記憶はすっかり抜け落ちていたので、たぶん僕の印象は非常に薄いものだったのでしょう。 ですが、その後、僕はアラン・チューリングについて少々知恵を付けたので、 新たにその目で見ると、イライラ・ポイント満載の再放送になってしまいました。

番組の制作スタッフの方々には申し訳ないのですが…

やはりこの番組シリーズ「やり過ぎちゃった科学者とその後の社会からの糾弾を含めて描く」ところに ドキュメンタリ番組としての面白さがあると僕は思うのですが、 チューリングの場合、本人は天才過ぎるし社会からの糾弾の方が酷すぎるので、 他の回とは違って「え?悪者は誰?」って迷ってしまう…この番組シリーズの 「面白さの方程式」が完全に崩れてしまったのが最大の問題点なんじゃないでしょうか? ドラマ・パートもインタビュー・パートも考証的にはしっかりした内容だっただけに残念な感じ。 たぶん「面白さの方程式」に素直に従うならマービン・ミンスキーを主役にした方が、 番組としては良い結果になったと思います。

結局「アラン・チューリングとは?」というと 「音楽家モーツァルト とよく似ている」という説明が一般にもわかりやすい答えかと思います。 すなわち「提案したアイデアがあまりにも革命的なだったので存命中はその価値は殆ど理解されず、 悲惨な最後を遂げたが、没後、社会が彼に追いつくにしたがって評価が年々高まってきている」 のではないかと思います。歴史的に見てもこんな見事な復活劇を演じた天才はそうそういません。 もちろん没後の話なので、ご本人にはちっとも嬉しくない話ですが…

あと、最後に、もうひとつだけイライラ・ポイントを書かせてもらうと…

番組中のキャプションで「世界初の人工知能宣言」というのがありますが、 チューリングの存命中には「人工知能」という言葉はなかったし、 彼自身は自分の研究を "Intelligent Machinery" と呼んでいたので ここは「知能機械」あるいは今風に「知能システム」の宣言と してもらいたかったところです。

もちろん一般には「人工知能」の方が通りが良いのでしょうけども…

以上



*1:当時、世界初のデジタル・コンピュータとされていた ENIAC の特許を巡って争われた 裁判 に起因してるのではないでしょうか。 事実、ENIAC後に登場した商用コンピュータはいずれも ENIAC特許に抵触する可能性がありました。 当時は社会的に影響の大きい訴訟と認識されていました。

*2:この恩赦の経緯はWikipedia英語版のチューリングのページにある "Government apology and pardon"(政府による謝罪) に簡潔にまとめられています。

*3:原文のリプリントが以下のURLで確認できます。

https://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf


*4:ジャック・コープランドが運営するウェブサイト AlanTuring.net で、原文の写真データが公開されています。

www.alanturing.net


*5:原文のリプリントが以下のURLで確認できます。

https://www.csee.umbc.edu/courses/471/papers/turing.pdf

また Wikipedia 英語版にはこの論文の解説ページがあります。

en.wikipedia.org

*6:もっとも、この論文に登場する"人間の審査員に『相手は人間だ』と誤解させる"ほどの対話システムを 開発するために、その後70年間も要している訳で…チューリングの論文の難解さとはこういうことなのです。

*7:原文は以下のURLで確認できます。

https://courses.csail.mit.edu/6.803/pdf/steps.pdf


*8:実は「機械学習」や「ニューラル・ネットワーク」も「人工知能」と 同じぐらい古くから存在した技術です。専門家の間では、2つをまとめて 「コネクショニズム」 という考え方と理解されてます。それと対照的な考え方があり、 日本では「記号処理」と呼ばれていました。

両者の関係をわかりやすく図解している記事を見つけたので紹介しておきます。

www.itmedia.co.jp


*9:この講演録は論文集 "A M. Turing's ACE Report of 1946 and Other Papers"採録されてますが、チューリングの講演録だけであれば "Lecture to the London Mathematieal Society on 20 February 1947" で読めます。 さらに素敵なことに日本語訳もあります。 「ロンドン数学会においてアラン・マジソン・チューリングが1947年2月20日に行った講演1)の日本語訳と注解

*10:原文のリプリントが以下のURLで確認できます。

https://weightagnostic.github.io/papers/turing1948.pdf


*11:コープランドの記事単体は以下のURLで閲覧できます。

http://www.cs.virginia.edu/~robins/Alan_Turing%27s_Forgotten_Ideas.pdf

最後の "Further Reading" を見てもらうとわかるように、 コープランドは1990年代にチューリング関連で幾つか論文を書いてます。 つまり彼はチューリング再評価の立役者のひとりと言えます。

*12:記事は以下で閲覧できます。

www.wired.com

www.wired.com


ローブナー・コンテストの歴史

1990年代のAIのパラダイム・シフトと 100年前の脚気原因論争と対比して


2020/06/18
藤田昭人


【注意】本稿は僕の思考プロセスをそのまま文に落としてしまいました。長いです。
【注意】長文を読みたくない方は結論だけご覧ください。



在宅勤務を始めて2ヶ月あまりが経ちますが…

最近、SNSを眺める時間が少し減っているように思います。 通勤の途中や外食のおりの待ち時間には決まって FacebookTwitter を開いていたのですが、 そうやって待ち時間の暇つぶしをする機会が減ってるってことでしょう。 もはや以前のようにSNSに齧り付くようなことはもうやってないですからね。

その反面、増えているのがテレビを視る時間。 スポーツ中継がなくなった穴を埋めるため 大量のドキュメンター番組を(再)放送しているBSをついつい視聴してしまいます。


フランケンシュタインの誘惑 科学史 闇の事件簿 「ビタミン×戦争×森鴎外

中でも以前からお気に入りのシリーズだった 『フランケンシュタインの誘惑 科学史 闇の事件簿』は 森鴎外の回を再放送してました。

www.nhk.jp

この回は「小説家 森鴎外」ではなく「軍医 森林太郎」としての業績(というか失敗)に 光を当てていまして、あらすじは次の Wikipedia ページのダイジェスト版といったところでしょう。

ja.wikipedia.org

このページにある盛り沢山の内容をわずか1時間の番組に収めるために 陸軍軍医 森林太郎 と 海軍軍医 高木兼寛 の対立の構図、それから高木の研究を発展させた 農学者 鈴木梅太郎オリザニン(ビタミン)発見 の顛末に絞った内容でした *1


「発見の科学」と「検証の科学」

この番組、僕はだいぶん前に視ていたのですが、 今回改めてみた再放送で一番印象深かったのはコメンテーターの 佐々木敏 先生のお話でした。

番組は明治時代に流行った脚気の原因について細菌説を主張した旧東京帝国大学医学部が 高木兼寛鈴木梅太郎の異説を頑なに拒絶した史実が元になっています。 森林太郎がクローズアップされたのは誰もが知る明治の文豪だったからで、 テレビ番組としての演出でしょう。この問題の本質であろう「細菌学 vs 疫学」の対立を 佐々木先生は「発見の科学」と「検証の科学」という立場の違いで説明されてました。 両者が各々重点におく「メカニズム」と「実証主義」といずれも重要で、 「両輪を同じ大きさにしていく努力が必要」と番組の終盤で語っておられました。

この「メカニズム」と「実証主義」のフレーズを耳にしたところでピンときて、 この脚気の原因に関する論争の構図をチューリングテストの論争に当てはめると わかりやすく整理できるなぁ…と考えたのでした。


「毎度、お騒がせ」だったローブナー・コンテスト

チューリングテスト *2 を使って「最も人間らしいチャットボット」を選ぶ ローブナー・コンテスト が今日のように世間の耳目に触れるようになったのは 21世紀になってからだと記憶しているですが、 実はコンテスト自体は1991年から毎年開催されていました。 言うなれば下積み時代のローブナー・コンテストは 「毎度、お騒がせ」だったことはスンドマンの記事 *3 が詳しいのですが、それによれば、 この賞を創設した ヒュー・ローブナー は周囲の人間には「付き合うのが面倒」と感じる気性なんだそうで、 その突飛な発想に周囲は振り回されることも一因だったそうです。

実際、当初のローブナー・コンテストは散々非難を浴びてきました。 前回 紹介した、 スチュアート・シーバー が書いた論文 "Lessons From a Restricted Turing Test" であるとか、 マービン・ミンスキー の "Minsky Loebner Prize Revocation Prize" だとか、 最初の10年間、ヒュー・ローブナーと ローブナー・コンテストの評判は 下がる一方だったそうです。

コンテストがこのような状況を脱し始めたのは リチャード・ウォレス が開発したチャットボット A.L.I.C.E. がコンテストで優勝した2000年以降のことでしょう。 正直「これは一体どう理解したら良いものだろうか?」と 僕はちょっと考え込んでいたのでした。


「毎度、お騒がせ」だった本当の訳

いろいろ調べてみたのですが、どうやら1991年に実施された第1回ローブナー・コンテストが全ての始まりだったようです。 このコンテストの模様は、 AIマガジンの1992年夏の号に掲載された記事 "The Quest for the Thinking Computer" で紹介されています。

aaai.org

この記事には、おそらく主催者サイドが広報的な目的で書いたと思われるので、 あまりネガティブな内容はありません。 ですが(前述の)スチュアート・シーバーは審判役で参加したのち このコンテストに批判的な意見を表明しました。 その主張を一言で説明すると「応募してきたチャットボットはどれもレベルが低すぎる」というものでした。 いずれも1966年に発表された ジョゼフ・ワイゼンバウムELIZA の方法を踏襲していると見て取れるので、 2〜3言やり取りをすればチャットボットだと見破れるわけだそうです。

この主張に ダニエル・デネット が率いるコンテストの運営委員会も同調したため、以降コンテストの主催者内部で紛糾する事になりました。 議論はすぐにヒュー・ローブナーとその他の委員会メンバーとの対立の構図となり、 やがてはチューリングテストの妥当性を巡って終わりのない議論に発展したようです。

実は1990年頃には「チューリングテスト懐疑論」が主流だった事実があります。 例えば、哲学者の ジョン・サール は1980年に発表した論文の中で 中国語の部屋 と呼ばれる思考実験を提案し「機械が考えることができるかどうかを決定するために チューリングテストを使うことはできない」と主張しました。

また 前々回 も紹介したように、1990年当時、チューリングの最大の貢献である暗号機 エニグマ を解読した Bombe は未だ軍事機密のままで その存在は噂話の域を出ませんでした。 それ故かチューリングの数々の業績は一般には正当に評価されていませんでした。 当時の世相を皮肉ってか、哲学者の ブレイ・ウィットビー は論文 「チューリングテストがAIの最大の袋小路である理由*4 の冒頭で「1973 - 1990: AIの研究者というよりも むしろ主に哲学者の気分転換のネタとなり始める」などと揶揄していました。

そういった世間のムードもあってか、 ローブナー賞委員会はコンテストからチューリングテストを排除する方向へと 誘導しようとしていたようですが、 コンテストの資金提供者であるヒュー・ローブナーは 「毎年、チューリングテストを実施する」 ことに固執し、委員会の提案を拒絶し続けました。 スンドマンの記事では ローブナーの次の発言が紹介されています。

The important thing was that the contest happen, not where it happened or who sponsored it.

If one thing makes Loebner see red, it’s the idea that his contest will not be held every year. “On this point I have been stalwart,” he said. “I have been adamant. I have been steadfast.”

重要なのは、コンテストはどこで開催されるか?誰が主催したのか?ではなく、コンテストが開催されるということだ。

ローブナーが顔を真っ赤にして怒る理由が一つあるとすれば、彼のコンテストは毎年開催されるわけではないということだ。「この点に関しては、私はかたくなだった」と、彼は言った。「私は断固として譲らない姿勢を貫いてきている」。

彼がなぜこれほどまでにチューリングテスト固執した理由はさだかではありませんが、 結局、1994年、第4回コンテストを前にして 運営委員会の委員が全員辞任するという事態に至り、 両者は喧嘩別れに終わりました。

マービン・ミンスキーが "Minsky Loebner Prize Revocation Prize" (ミンスキー・ローブナー賞取り消し賞)を言い出したのは、 その翌年の1995年のことでした。 スンドマンの記事では「事実上ローブナー・コンテストのボイコットを促すような行為」と紹介しています。 確かに「恥をかかされたAIコミュニティによる報復」と受け止められても致し方ないやり方です。

正直、当初「『人工知能の父』と称されるミンスキーが そんな子供っぽいことをやるのかな?」と個人的には思っていたのですが、 脚気原因論争の話から図らずも「むしろ権威を背負ってしまってるから、 引くに引けなくなってしまった」のだと得心してしまいました。 確かに、表向きは「脚気細菌説」を主張しながら 巧みに「脚気栄養欠陥説」を当てこすった ミンスキーと森林太郎とが重なって見えます。


AIのパラダイム・シフト

ミンスキーと森林太郎とのもうひとつの共通点は 「自らの主張が徐々に受け入れられなくなっていく様を眺めているしかなかった」 ことです。Wired の2003年5月13日の記事 "AI Founder Blasts Modern Research" では、 ミンスキーが孤立を深めつつある様子が伺えます。

www.wired.com

この記事によれば、ミンスキーが第1次AIブームでの自然言語処理、 第2次AIブームでのエキスパートシステムの延長上にAIの将来を見ていたように理解できます。 しかし現実のAIのトレンドは別の未来に向かっていたようです。 ミンスキーの批判の矛先になった ロドニー・ブルックス は「マーヴィンの批判を私に向けていたのかもしれない」とミンスキーの批判を受け流し、 次のような反論を繰り出しています。

"Not all of our intelligence is under our conscious control," said Brooks. "There are many layers of intelligence that don't require introspection." In other words, the emphasis on common-sense reasoning doesn't apply to some efforts in the AI field.

「私たちの知性のすべてが意識的なコントロール下にあるわけではない」とブルックスは言う。 「内省を必要としない知性の層がたくさんある」 つまり、常識的な推論を重視することは、AI分野の一部の取り組みには当てはまらないということだ。

ブルックスは1986年に論文 "a robust layered control system for a mobile robot" (移動ロボット用の堅牢な階層化制御システム)で知能ロボットに関する 「包摂アーキテクチャ」 (SA: subsumption architecture) 理論を提唱し、 1991年には論文 "Intelligence without representation" (表象なき知能)を発表して『知能にとって、表象は不要である』という「反表象主義」の立場を打ち出しました。 ブルックスの研究については松岡正剛氏の 『ブルックスの知能ロボット論』の書評 が簡潔で比較的わかりやすいです。 「包摂アーキテクチャ」と「表象なき知能」の説明を次に抜き出してみました。

多様なエージェント機能を分散自律的に並列処理できる端的ロボットの開発をめざしたのだ。 そこで提案したのが「サブサンプション・アーキテクチャ」(Subsumption Architecture)である。 包摂アーキテクチャと訳されたり、略してSAと呼ばれてきた。
複雑で知的な動作をはたすべきロボットのしくみを、 あらかじめ単純なモジュールに分割しておいて階層化し、 これを動作の進展にあわせて優先順位がつくように仕立てて 自律性を発揮するように仕向けようという設計思想のことをいう。

(中略)

サブサンプション・アーキテクチャにもとづくロボットには 必ずしも高度な知能をもたせる必要はない。 知的な行動や判断ができるための設計をすればよい。表現力もいらない。 「表象なき知性」(intelligence without representation)だけでいい。 そういう思想だ。ブルックスはそのことを「虫」のふるまいに学んだ。 昆虫は神経節しかもっていないが、それでも包摂環境を判定して自在に動く。 そこに注目したわけである。

なるほど、ミンスキーが「包摂アーキテクチャ」を嫌った理由がよくわかります(笑)

そもそも、ミンスキーにとって最終的な研究目標は 2001年宇宙の旅 に登場する HAL9000 のようなコンピューターだったように僕は想像しています *5。 なので、ブルックスが提唱する昆虫のようなロボットは、 ミンスキーにとって取るに足らない研究テーマに見えた事でしょう。 ところが彼のお膝元であるMITのAIラボですら学生は昆虫作りに熱中する始末。 それというのも(今では日本でもテレビCMをよく見かける) 自動掃除機「ルンバ」 *6というコンシューマ製品が実用化された事例があるからでしょう。 この全く不愉快な現実に散々憂さを溜め込んでいたミンスキーが とうとうブチ切れた・・・というのがWiredに掲載された会議の 真相だったのだろうと僕は想像してます。

これ、脚気の原因を突き止めるための疫学的アプローチに似てませんか? また、細菌学に基づいて研究を重ねても重ねても答えが全く見つからない中、 やがては「兵隊には麦飯さえ食わせておけば脚気にはならんのでは?」と 至る所で陰口を叩かれるようになった時の森林太郎の心情を ミンスキーのこの「ブチ切れ」から推し量ることができるような気が僕はするのです。


リチャード・ウォレスが語るブルックスのAI論とチャットボットの関係

ブルックスは、1990年(最初のローブナー・コンテストの1年前)に最初のベンチャー iRobot を創業します。つまりローブナー・コンテストは最初から 彼らが引き起こすAIのパラダイム・シフトに遭遇し、 その影響を受けること予期されたことになります。

ローブナー・コンテストとロドニー・ブルックスとの接点はないかと探してみたところ、 スンドマンの記事で1箇所だけ「ロドニー・ブルックス」と言及されているところを見つけました。 それはチャットボット A.L.I.C.E. の開発者であるリチャード・ウォレスに対するインタビューです。

I asked him where he got the inspiration for ALICE. He said that he had been influenced by the "minimalist" A.I. ideas associated with Dr. Rodney Brooks of MIT's A.I. lab.

At first, he said, he had tried to follow some of the more grandiose theories of traditional A.I., but he found them sterile. "You read a book with a title like 'Consciousness Explained,'" he said, "and you expect to find some kind of instruction manual, something that you can use to build a consciousness. But of course it's nothing of the kind." (Daniel Dennett wrote "Consciousness Explained.")

ALICEのインスピレーションはどこから来たのか聞いてみたところ、 MITの人工知能研究所のロドニー・ブルックス博士が提唱する「必要最低限度の」AIの考え方に影響を受けたという。

最初は、伝統的なAIの壮大な理論のいくつかに従おうとしたが、それは不毛だと思ったという。 彼が言うには「もし "Consciousness Explained" のようなタイトルの本を読んだとしたら、 ある種の手順書や意識を構築するために使用できる何かを期待するでしょう。 しかし、もちろんそのようなものは何もありません」 ("Consciousness Explained"『意識の説明』はダニエル・デネットの著作)

インタビュー自体はこの後デネットの著作のタイトルに触発されてか「意識」の議論へと移って行きますけども…仮定が多く具体性に欠くメカニズムを語るデネット(やミンスキー)のAI論よりも、単純で明快なブルックスのAI論を当時のプログラマーが支持し始めていた事がうかがえる発言ですね。

さらにウォレスが A.L.I.C.E. を開発する際に考えていたことをもう少し知りたいと思い、 文献を探してみたところ、ウォレスのインタビュー記事が結構たくさん出回っている事がわかりました。 その全部をここで披露すると長くなり過ぎるので、とりあえず ニューヨークタイムズのインタビュー記事 だけを紹介しておきます。A.L.I.C.E. の最初のバージョンが誕生したときのエピソードです。

Alice came to life on Nov. 23, 1995. That fall, Wallace relocated to Lehigh College in Pennsylvania, hired again for his expertise in robotics. He installed his chat program on a Web server, then sat back to watch, wondering what people would say to it.

Numbingly boring things, as it turned out. Users would inevitably ask Alice the same few questions: ''Where do you live?'' ''What is your name?'' and ''What do you look like?'' Wallace began analyzing the chats and realized that almost every statement users made began with one of 2,000 words. The Alice chats were obeying something language theorists call Zipf's Law, a discovery from the 1930's, which found that a very small number of words make up most of what we say.

Wallace took Zipf's Law a step further. He began theorizing that only a few thousand statements composed the bulk of all conversation -- the everyday, commonplace chitchat that humans engage in at work, at the water cooler and in online discussion groups. Alice was his proof. 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.

Wallace had hit upon a theory that makes educated, intelligent people squirm: Maybe conversation simply isn't that complicated. Maybe we just say the same few thousand things to one another, over and over and over again. If Wallace was right, then artificial intelligence didn't need to be particularly intelligent in order to be convincingly lifelike. A.I. researchers had been focused on self-learning ''neural nets'' and mapping out grammar in ''natural language'' programs, but Wallace argued that the reason they had never mastered human conversation wasn't because humans are too complex, but because they are so simple.

"The smarter people are, the more complex they think the human brain is," he says. "It's like anthropocentrism, but on an intellectual level. 'I have a great brain, therefore everybody else does -- and a computer must, too.'" Wallace says with a laugh. "And unfortunately most people don't."

アリスは1995年11月23日に誕生した。 ウォレスはその秋、ペンシルバニア州のリーハイ大学に移り、ロボット工学の専門知識を買われて再就職した。 彼はウェブサーバーにチャット・プログラムをインストールし、人々が何を言うのかと思いながら椅子に座って眺めていた。

結局のところ、それは非常に退屈なものだった。 ユーザーは必然的にアリスにも同じような質問をすることになる。 「どこに住んでるの?」「あなたの名前は?」「あなたの外見は?」 ウォレスはチャットを分析し、ユーザーが発するほとんどすべての発言が2,000語のうちのいずれか1語から始まっていることに気づいた。 アリスのチャットは、1930年代に発見された言語理論家がジップの法則と呼ぶものに従っていた。 非常に少数の単語が私たちの会話の大部分を構成していることがわかった。

ウォレスはジップの法則を一歩進めた。 彼は、職場や冷水器、オンラインでのディスカッション・グループといったところで、 人間が日常的に行うありふれたおしゃべりなど、すべての会話の大部分をわずか数千の発言から構成されているという理論を立て始めた。 アリスが彼の根拠だった。 もしアリスが質問で混乱しているのを見ればそのたびに、彼がアリスに新しい答えを教えたとしたら、 彼は最終的には一般的な発言すべてをカバーするだけなく、さらには多くの変わった発言もカバーするだろう。 ウォレスは、マジックナンバーは約4万件の応答だと考えている。 これだけプログラムされた発言ができるようになれば、 アリスに向かって(あるいは「彼女は」と呼びはじめたときには)人々が言っていることの95%に反応できるようになる。

ウォレスは、教育を受けた知的な人々を落ち着かない気分にさせる理論を思いついた。 会話とは、同じ発言を何度も何度も何度も何度も繰り返すだけで、それほど複雑ではないかもしれない。 ウォレスが正しければ、人工知能は、人間を納得させるように生きていくために、特に知的である必要はなかった。 A.I.研究者たちはこれまで、自己学習「ニューラルネット」と「自然言語」プログラムの文法に焦点を当ててきたが、 ウォレスによると、彼らが人間の会話をマスターできなかったのは、人間が複雑すぎるからではなく、単純すぎるからだという。

「頭の良い人ほど人間の脳は複雑だと思っています」と彼は言う。 「人間中心主義に似ていますが、それは知的レベルの問題です。 『僕は頭がいいから、ほかのみんなもそうする。コンピュータもそうしなければならない』」とウォレスは笑う。 「残念ながらほとんどの人はそうではありません」

いかがでしょう?ウォレスが語る「ブルックスの必要最小限度のAI」の考え方が なんとなくわかって来たような気がしませんか?

僕なりに砕けて解釈させてもらうと「傍目に知能があると見えるのなら、 何も無理してメカニズムさえわからない知能を実装せんでもよくねぇ?」と ある種の開き直りのようなアプローチが、ブルックスの 「包摂アーキテクチャ」や「表象なき知能」の革新性なんでしょう。 この「知能があるかのように見せかける」アプローチは ワイゼンバウムのELIZA論文*7 を彷彿させます。

結局 A.L.I.C.E. はマークアップ言語で記述するスクリプトを用いる 「モダンな ELIZA」とも言うべき設計になっています。 そして ELIZA から進化させた主な要因は「4万件もの大量の応答メッセージを用意する」ことだったようです。 それ故に A.L.I.C.E. の革新性は非常に分かり辛いものになっていますが…

  • チャットボットに対する人間の発言はジップの法則に従う
  • ジップの法則にしたがって十分に大量の応答メッセージを用意すれば人間のどんな発言の概ね適切に対応することができる

という「スクリプト作成に重点をおいたチャットボット開発」を提案したことなのでしょう。 この提案はウォレスが1995年あたりに行った作業だといいますから 今日でいうところのデータ・サイエンスと呼ばれる領域の先鞭をつける試みだったと言えます。

またローブナー・コンテストの視点で語れば、 シーバーに「ELIZAに毛の生えた程度」と論評された 第1回コンテストに参加したチャットボットの性能改善の方策は 「応答メッセージを大量に増やせ」ということになります。 もっとも経験した方には分かって貰えると思いますが、 万のオーダーで重なりのない応答メッセージを定義することは かなり苦痛が伴う作業でもあります。これは正しく実証主義的アプローチです。

脚気原因論争の構図に当てはめると…こじつけっぽくなるので、もうやめておきましょう。


なんだか…

長くとっ散らかった話になってしまいましたが、あと少しお付き合いください。

脚気原因論争の枠組みを辿ってローブナー・コンテストの諸事件を整理してみたら、 ロドニー・ブルックスの新しいパラダイムやリチャード・ウォレスの仕事の意義に辿りつけたのはラッキーでした。

あまり有名ではないトピックで文献が集まらず因果関係が埋没してしまう場合、 僕はまったく関係のないトピックの枠組みだけを拝借して見つけるべき文献をヒントを捻り出します。 これも『Unix考古学』を書いてる時に編み出したテクニックなんですけども、 そのデメリットはまんま書き下ろすと相当読みづらい記事になってしまうこと。 まぁ、ここに公開してるのはあくまでも草稿なんで…この記事を書籍向けに採用する場合は第2稿を書き下ろさないと。

しかし、90年代に起こったAIのパラダイム・シフトは僕にとって盲点でした。 同世代の皆さんも同じなんじゃないかと思いますが、 僕らは第二次AIブームをリアルタイムで経験していて、 マッカーシ・ミンスキーに対する擦り込みが強烈だったのです。 なので、僕らが知る人工知能と現在もブームが続くAIには大きなギャップを感じてしまう。 皆さんの周りにもそういう老人はいませんか?

このギャップ、AIの専門家の間では 「トップダウン・アプローチ vs ボトルアップ・アプローチ」と呼ぶトピックで語られてきたものです。 皆さん「どちらが正しいか?」との議論は大好きですが、結論を出す事を必ず避けるので、 いつもモヤモヤして終わる後味の悪い議論になります。なので 「スーパーエリートの森鴎外でもしくじった」 という歴史的評価を借りたかったのです(笑)

さて、脚気原因論争とローブナー・コンテストの各々についてオチをつけましょう。


脚気原因論争の顛末

脚気原因論争は、新しい栄養素ビタミンの発見により「脚気栄養欠陥説」が正しいことで、決着が付きました。 陸軍の食事も白米から(正直あまり美味しくない)麦飯に切り替わったそうです。 もっとも激烈なポジショントークを連発し続けた森林太郎が軍医を退官するまで、 陸軍は脚気細菌説を取り下げることはできなかったとか。

番組では、旧東京帝国大学医学部が鈴木梅太郎オリザニンを意図的に無視した事により ノーベル賞を取り損なったとの批判も語られてましたが、 過去のノーベル章の選考では鈴木の例に限らず様々な偏向があったことは今ではよく知られている *8 ので、仮に旧東京帝国大学医学部の妨害が無かったとしても、結果は変わらなかったかも知れません。

その鈴木梅太郎が米糠の脚気に対する効用を検証するための臨床試験に苦労していた1910年代、 ノーベル賞にもっとも近い日本人と言われていたのが 野口英世 でした。鈴木より2歳若い野口は当時、 アメリカのロックフェラー医学研究所に所属し、 1914年、1915年、1918年のノーベル医学賞候補になりました。 しかし、野口が1度も受賞できなかったのは皆さんご存知のとおりです。

調べてみるとこの時期のノーベル化学賞あるいはノーベル生理学・医学賞の受賞者はいずれもヨーロッパ人で、 日本人だけでなくアメリカ人も含めまだ過去にひとりも受賞者はいませんでした。 ちなみに野口が候補者にノミネートされたこの時期、 ヨーロッパは第1次世界大戦(1914〜1918)の最中でした*9。 つまり、野口や鈴木は日本人だったから受賞できなかったのではなく、 20世紀初頭のノーベル賞はまだまだ19世期の世界観が色濃く残った ヨーロッパ世界限定の賞だったというのが正しい理解のようです。


ローブナー・コンテストのその後

委員全員の辞任やミンスキーの難癖に見舞われた後も ゴタゴタ続きのローブナー・コンテストは 2003年〜2004年に最大のピンチを迎えます。 当初からコンテストの運営を委託されていた ケンブリッジ行動研究センター(CCBS)は コンテストの企画・運営に行き詰まりを感じ、 スポンサーであるヒュー・ローブナーとの委託契約の解消へと動きます。 本稿でも多数引用して来たスンドマンの記事は ヒュー・ローブナーと CCBS との交渉が大詰めを迎えていた 2003年に取材・執筆されたこともあって、 この委託契約解消のトラブルの顛末には詳しいです。 コンテストの運営主体を失ったヒュー・ローブナーは それでもコンテストの毎年開催を諦めず、 なんと2004年は会場として自宅を開放してコンテストを開催し続けました。

このようなコンテストの苦境を救ったのは チューリングの母国であるイギリスのチューリング・フリークでした。 チューリングに関する国際会議やイベントに併設される形で ローブナー・コンテストは毎年実施され続けました。 さらにヒュー・ローブナーが亡くなる2年前の2014年からは 世界最古の人工知能学会である Society for the Study of Artificial Intelligence and the Simulation of Behaviour (AISB) がコンテストの運営を引き継ぎ、今日に至っています。

コンテストに応募するチャットボットの性能も リチャード・ウォレスの A.L.I.C.E. の優勝以来、格段に進化し、 開発者共々マスメディアへの露出も増えましたが、 それはガーディアンなどイギリスのメディアによる 報道であることが多いように思います。


最後に…ローブナー・コンテストとAIパラダイム

20世期と21世期を跨ぎ30年間続けられて来たローブナー・コンテスト。 その「毎年、チューリングテスト」とのブレない姿勢ゆえに、 かえってその時々の様々な技術トレンドを浮き上がらせる結果になったのだろうと僕は想像しています。 本稿では特にコンテストの企画・運営に大きな影響を与えた運営委員会にフォーカスしてきました。 初期の委員会が全員辞任により消滅して以降、運営委員会は各回ごとその都度組織されいたようで、 現在はAISBがその任に当たっているのだと思います。

コンテストの長い歴史を振り返ると、運営委員会が信じるAIパラダイムは 世紀の変わり目あたりに断絶があるように僕には見えます。 言わば「20世紀のAIパラダイム」と「21世紀のAIパラダイム」には大きな差違があるといった感じでしょうか? これ、AIの世界では昔から「トップダウン・アプローチ」と「ボトムアップ・アプローチ」などと呼ばれて来たように思います。 前者がその真偽はさておき数学のように仮説的な「メカニズム」ありきで考察から始めるのに対し、 後者は原則として人間の脳や神経回路の仕組みを調べて模倣する「実証主義」を貫く違いがあります(と僕は理解してます)。

もっとも、この用語は困ったことに時代によって意味が変わるのです*10。 1960年代生まれの僕の世代では「トップダウン・アプローチ」に分類される 「意思決定アルゴリズム」や「自然言語処理」「エキスパートシステム」こそが「人工知能」だと習いました。 一方の「ボトムアップ・アプローチ」に分類される「ニューラル・ネットワーク」や「機械学習」は (サイバネティックスの影響からか)「制御理論」と説明されてました。 ところが、1980年以降に生まれた諸君は「トップダウン・アプローチ」の具体例をほとんど知りません。 どちらかと言えば「ボトムアップ・アプローチ」に分類される 「サブサンプション・アーキテクチャ」や「ディープラーニング」を「人工知能」だと理解しているようです。

本稿では、かなり回り道をしながら、1990年代にAIのパラダイムが 「トップダウン・アプローチ」から「ボトムアップ・アプローチ」へとシフトしていったことを紹介しています。 (これで残りの部分も読む気になりましたか?) AIのパラダイム・シフトを観測するポイントのひとつとして、 ローブナー・コンテストの歴史を辿るのはちょうど良いかも知れません。

コンテストに参加したチャットボットの技術的な特徴など、 さらに踏み込んでいろいろ調べてみたいと思わず考えてしまいますが、 本稿はもう十分に長くなってしまっているので、ここで一旦終わりとします。

以上

*1:鈴木梅太郎の話は 栄光なき天才たち のエピソードの一つとしても発表されてますね。

*2:このブログでは元祖チャットボットのELIZAと その評価方法であるチューリングテストをクドクドと扱ってきてますが…

そもそも「チューリングテスト」とは、 アラン・チューリングが "Can machines think?" (「機械は考えることができるか?」) という問いを確かめるための手段として、 次のような "The Imitation Game"(「模倣ゲーム」)を提案したことに始まります。

  • 男性(A)、女性(B)、質問者の3人で行われる
  • ゲームの目的は、質問者が、どちらが男性で、どちらが女性かを当てること
  • 質問者は文字だけによる会話を通じて判定を行う
  • Aの男性は、質問者を間違わせるように振舞う
  • Bの女性は、質問者を助けるように振舞う

さて、この「模倣ゲーム」でAの役割を機械が受け持ち、 質問者は人間と機械の判定を行うとしたらどうなるだろうか?

このチューリングが考え出した、 それこそホームパーティでよく行われる ちょっとしたゲームのようなテストに、 その後70年間も人類は悩まされ続けてる訳です(笑)

*3:前回取り上げたジョン・スンドマンの記事『Artificial stupidity』を再掲しておきます。

Artificial stupidity | Salon.com

Artificial stupidity, Part 2 | Salon.com

かつての「毎度、お騒がせ」だった時代の ローブナー・コンテストの実相について語る 貴重な情報源です。

*4:原題は "Why The Turing Test is AI's Biggest Blind Alley"。
この論文は1997年とクレジットされてますが、 Turing 1990 Colloquium での発表内容なんだそうです。

*5:事実、ミンスキーは科学的考証の立場で この映画の制作に参加してました。

*6:ルンバの設計については、 松岡さんが書評でわかりやすい説明をしておられるので、 是非ご一読を。

*7:このブログを始めた頃に書いた ELIZAの紹介 の記事をご覧ください。

*8:科学者として最高の栄誉と認知されているノーベル賞は 今でもメディアの大きな話題となりますが、 過去の選考には様々な醜聞もあります。 中でも僕が一番酷いと思うのは1962年のノーベル生理学・医学賞の受賞者 ジェームズ・ワトソンフランシス・クリックモーリス・ウィルキンス のケースです。

彼らは、DNAの 二重螺旋構造 を発見したことが受賞理由だったのですが、 この発見の決定的な証拠となったのは ロザリンド・フランクリン が撮影したX線回折写真でした。 フランクリンは1958年に病没していたためノーベル賞は受賞できなかったとされてましたが、 1953年にこの発見を公表した 論文『デオキシリボ核酸の分子構造』 はワトソンとクリックの署名しかなかったことから、 フランクリンが撮影した写真の入手方法が大問題となりました。 一説によればフランクリンと同僚だったが個人的に折り合いの悪かった ウィルキンスが彼女には内緒でワトソンとクリックの元に持ち込んだと囁かれています。

背景として著名な研究機関でも1950年代にはまだ、 人種的な差別が横行し、女性研究者の地位も低かったことが上げられています。

*9:この事実もなんらかの因果関係がありそうですが…それはまたの機会にしましょう。

*10:改めて調べてみたところ僕らの世代が習った「トップダウン」と「ボトムアップ」とは ジャック・コープランド による次のような説明でした。

Top-Down AI vs Bottom-Up AI -- What is Artificial Intelligence?

Turing's manifesto of 1948 distinguished two different approaches to AI, which may be termed "top down" and "bottom up". The work described so far in this article belongs to the top-down approach. In top-down AI, cognition is treated as a high-level phenomenon that is independent of the low-level details of the implementing mechanism -- a brain in the case of a human being, and one or another design of electronic digital computer in the artificial case. Researchers in bottom-up AI, or connectionism, take an opposite approach and simulate networks of artificial neurons that are similar to the neurons in the human brain. They then investigate what aspects of cognition can be recreated in these artificial networks.

1948年のチューリングマニフェストは、「トップダウン」と「ボトムアップ」と呼ばれる、 AIへの2つの異なるアプローチを区別しています。 これまでに紹介した作品は、トップダウン型のアプローチに属しています。 トップダウンAIでは、認知は、実装メカニズム(人間の場合は脳、人工の場合は電子デジタルコンピュータの一つ以上の設計など) の低レベルの詳細とは独立した高レベルの現象として扱われます。 ボトムアップAI(コネクショニズム)の研究者たちは、逆のアプローチをとり、 人間の脳のニューロンに似た人工ニューロンのネットワークをシミュレーションしています。 そして、これらの人工ネットワークで認知のどのような側面が再現されるかを調査します。

ここで登場する "Turing's manifesto of 1948" とは、論文 "Intelligent Machinery" のことのようです。コープランドによればチューリングはその時の上司の反対にあって、 この論文を寄稿しなかったらしいので、 一般が知るようになったのはチューリングの没後のことでしょう。

しかしながら、チューリングが「トップダウン」と「ボトムアップ」にまで言及していたとは驚きです。

It is humbling to read Alan Turing's papers. He thought of it all. First.

アラン・チューリングの論文を読むのは屈辱的だ。彼はすべてを考えた、誰よりも先に。

とのロドニー・ブルックスのコメントに思わず肯いてしまいます。