Write and Run

it's a simple way, but the only way.

日本語の形態素解析以外にもMeCabを使う、またはMeCabの辞書の仕組み

みなさん、和布蕪は好きですか。私は食べたことがありません。

さて、MeCab は優秀な日本語の形態素解析機として有名ですが、みなさんは MeCab をそれ以外の用途で使ったことがありますか。食わず嫌いは感心できませんねぇ。

日本語の形態素解析機としての振る舞いは MeCab のほんの一面に過ぎません。MeCab はいつも読んでる IPAdic が何語の辞書かなんて知りませんし、日本語の文法がハードコートされているわけでもありません。MeCab は、振る舞いの全てを辞書に決められているといっても過言ではないほど、辞書によって様々な「言語」を解析できるようになります。ここでいう「言語」とは、記号の並びの規則、またはその規則に則って並べられた記号列のことだと思ってください。つまり、「辞書」は言語の語彙だけでなく、規則を記述する能力を持っているのです。

MeCab の「辞書」に含まれる情報に軽く触れておきましょう。まずはいわゆる辞書、語彙セットです。これには必須の情報として「表層形」「左文脈 ID」「右文脈 ID」、それから「コスト」の4つが含まれています。IPAdic を用いて日本語を形態素解析したときの品詞やらという情報はこの4つの情報に追加で MeCab が読まないメタデータとして載っています。MeCab からしてみたら品詞がどうとか知ったことではないわけです。

表層形* 左文脈 ID* 右文脈 ID* コスト* ここから先はメタデータ
1285 1285 594 名詞,一般,,,,,本,ホン,ホン

じゃあどうやって形容詞は名詞にかかるだの、連用形のあとには用言が来るだのといった文法を MeCab は理解しているのでしょうか。そこで登場するのが「左文脈ID」「右文脈ID」です。

「辞書」には語彙の他に、連接表*1というものがあります。これには「左文脈 ID」と「右文脈 ID」のすべての組み合わせに関して、その組み合わせのコストが書かれています。品詞の細分類ごとに文脈IDを振ればそれらの組み合わせについてコストを決定することでなんとなく文法を定義できそうですよね。だんだんわかってきましたね。実際、IPAdic では、すべての品詞細分類に ID が振られており、その数なんと1316。品詞分類をもっと細かくした UniDic では5981種類もの ID があります。

文脈IDの「右」やら「左」とは一体何者なのでしょうか。A B という単語(形態素)の並びのコストを求めてみましょう。

A B
左文脈 ID
100
右文脈 ID
300
左文脈 ID
200
右文脈 ID
400
A→B のコストはこの
組み合わせを見る

「左の単語の右の ID」と「右の単語の左の ID」の組で表を読み、そこに書いてあるコストを読みます。この場合ですと、300 200 ですから、その行を連接表から探します。

右文脈 ID 左文脈 ID 連接コスト
…前略…
300 200 -50
…後略…

無事、A B の連接コストは -50 であることがわかりました。

なぜ左と右に ID があるのかという疑問に答えてくれる資料は見つからないわけですが、Python による日本語自然言語処理によれば、

品詞は形態素を左から見た場合と右から見た場合では違うものとして扱うのが一般的である

らしいので、そういうことだと思いましょう。しかしというべきかやはりというべきか、残念ながら IPAdic も UniDic も左文脈 ID と右文脈 ID には同じ値が入っており、あまり有効活用されていない仕組みです。

本題

MeCab の辞書の仕組みがわかったところで、やっと本題です。

マレー語(マレーシア語)の接頭辞・接尾辞と語幹を分割するために MeCab を使ってみます。IPAdic を食わせた MeCab は日本語の「文」を解析するわけですが、ここではマレー語の「単語」を解析します。

マレー語の単語は接頭辞・語幹・接尾辞からなります。また、接頭辞・接尾辞は原理的には複数個つくこともありますが、つかないかあるいは1個だけのことがほとんどです。また、語幹の頭文字によって接続できる接頭辞が異なることがあります。

さて、これらのルールを踏まえた上で、マレー語の単語用の連接表を作っていきましょう。

まず素性の種類を列挙します。Excel とかで適当にやります。

素性1 素性2
…前略…
prefix ber
prefix be
prefix di
prefix diper
prefix dipe
prefix kau
…中略…
headword *
headword r
headword b/f/v
headword d/c/j/z/sy
…後略…

語幹(ここでは headword と呼んでます)を接頭辞が接続できる頭文字ごとに分類しておくのがコツです。

あとは、これらの素性の種類同士すべての組み合わせに関して連接表を書けばいいだけです。「だけ」とはいうものの、結構なパターン数です。実際のファイルで43種類ありました。つまり、432で1849通りの組み合わせについて連接表を書く必要があります。あまり手作業ではやりたくないですね。

そこで、以下のようなルール表を用意して自動化します。

左素性1 左素性2 右素性1 右素性2 連接コスト
…前略…
prefix be headword r 50
prefix be headword 1000
prefix dipe headword r 50
prefix dipe headword 1000
prefix mem headword b/f/v 50
prefix mem headword 1000
…後略…

上のほうが優先順位が高く、空欄は任意の値であることを表しています。このルール表をすべての組み合わせに対して適用します。書き捨てのプログラムをガッと書いて適当にやりましょう。

word_class_list_file = open(File.expand_path('./word_class_list.txt', __FILE__))
rule_file = open(File.expand_path('./rule.txt', __FILE__))

matrix_file = open(File.expand_path('./matrix.def', __FILE__), 'w')
left_id_file = open(File.expand_path('./left-id.def', __FILE__), 'w')
right_id_file = open(File.expand_path('./mecab_dict/right-id.def', __FILE__), 'w')

WORD_CLASSES = word_class_list_file.each.with_index.map do |line, index|
  word_class = line.rstrip.split("\t")
  [index, word_class]
end

RULE = rule_file.each.map do |line|
  *pattern, cost = line.rstrip.split("\t", -1)

  left, right = pattern.map{|col| col.length == 0 ? nil : col }.each_slice(2).to_a

  [left.reject(&:nil?), right.reject(&:nil?), cost]
end

SORTED_RULE = RULE.sort_by {|(left, right, _)| [left.length,  left, right.length, right] }.reverse

matrix_file.puts("#{WORD_CLASSES.length} #{WORD_CLASSES.length}")
WORD_CLASSES.product(WORD_CLASSES).map do |(left_id, left_class), (right_id, right_class)|
  matched = SORTED_RULE.lazy.
    select {|(left, _, _)| left_class[0...left.length] == left }.
    find {|(_, right, _)| right_class[0...right.length] == right }

  _, _, cost = matched

  matrix_file.puts("#{left_id} #{right_id} #{cost}")
end

WORD_CLASSES.each do |(index, word_class)|
  line = "#{index} #{word_class.join(',')}"
  left_id_file.puts(line)
  right_id_file.puts(line)
end

上で用意した2つの表をそれぞれ word_class_list.txt, rule.txt という名の tab 区切りのテキストファイルとして保存して実行すると、matrix.def, left-id.def, right-id.def が吐き出されます。2千行弱のテキストファイルを人間の手で書くなんてことをする必要はありません。

あとは語幹(単語)リストのファイルを用意すれば辞書は完成です。お疲れ様でした。

まとめ

  • MeCab は辞書の変更だけで任意の言語に適用できるような柔軟な設計である
  • さらっと書いたけど、素性分類のコツが全て

*1:matrix.def というファイルで定義されている
参照: MeCab の辞書構造と汎用テキスト変換ツールとしての利用

就活目的 #cdgh

最高だぞ

f:id:koba789:20161118215448j:image

次回は今回の人間の知り合いの会社らしい

 

f:id:koba789:20161118215627j:image

 俺はオレンジ

 

お前ら行け

f:id:koba789:20161118222721j:image

これは聖地渋谷までわざわざ行って撮った

 

インスパイアされた次回

夜の合同説明会 - アニメイトラボ, ドリコム, GMOペパボ, Repro - connpass

 

他の就活目的レンジャー:

就活目的、夜の合同説明会 - osyoyu.hatenablog.com

就活を目的させていただきます - 思考録++

#cdgh 夜の合同説明会の感想 - polamjaggy

 

入隊したい方はこちら:

就活戦線2016 - polamjaggy

TypeScript+mocha+power-assert+empowerでカバレッジをとってついでにCircleCI+Codecov

表題のとおりだが、更に CircleCI のビルドを高速化するために yarn を使うぞ。

やりたいこと

  • 最新の TypeScript を使う
    • 1.8 とか使わない
  • テストコードも TypeScript で書く
  • empower する
    • 明示的に require('power-assert') しない
  • source map が生きてる状態でカバレッジを取る
    • TypeScript の行ベースで結果が出る

やりたくないこと

  • 中間ファイルの生成

やっていく

ガンガン入れる。

yarn add --dev typescript ts-node babel-register babel-preset-power-assert power-assert mocha nyc @types/mocha @types/node

この状態で、

mocha --compilers ts:ts-node/register,babel-register

とするとテストが、

nyc --extension .ts mocha --compilers ts:ts-node/register,babel-register

とするとカバレッジ付きのテストが走るようになっている。

なので、package.jsonscripts にこれを書く。

    "test": "nyc --extension .ts mocha --compilers ts:ts-node/register,babel-register"

いいですね?

ここからは CI の話

package.jsonscripts にこれを足す。

    "report-coverage": "nyc report --reporter=lcov  > coverage.lcov"

これでカバレッジのレポートが吐けるようになった。

カバレッジレポートを codecov に送りつけるために circle.yml にこれを書く。

test:
  post:
    - npm run report-coverage && bash <(curl -s https://codecov.io/bash)

あと、CircleCI でのビルドを高速化したいので以下の内容もベタっと書く。

machine:
  node:
    version: 7.0.0

  post:
    - curl -o- -L https://yarnpkg.com/install.sh | bash

dependencies:
  cache_directories:
    - "~/.yarn-cache"

  pre:
    - yarn --version

  override:
    - yarn install

blog.stormcat.io

この記事を参考にした。

結果

package.json

{
  "scripts": {
    "test": "nyc --extension .ts mocha --compilers ts:ts-node/register,babel-register",
    "report-coverage": "nyc report --reporter=lcov  > coverage.lcov"
  },
  "devDependencies": {
    "@types/mocha": "^2.2.32",
    "@types/node": "^6.0.46",
    "babel-preset-power-assert": "^1.0.0",
    "babel-register": "^6.18.0",
    "mocha": "^3.1.2",
    "nyc": "^8.3.2",
    "power-assert": "^1.4.1",
    "ts-node": "^1.6.1",
    "typescript": "^2.0.6"
  }
}

circle.yml

machine:
  node:
    version: 7.0.0

  post:
    - curl -o- -L https://yarnpkg.com/install.sh | bash

dependencies:
  cache_directories:
    - "~/.yarn-cache"

  pre:
    - yarn --version

  override:
    - yarn install

test:
  post:
    - npm run report-coverage && bash <(curl -s https://codecov.io/bash)

最後に

あとはバッジとかつけてかっこよくキメましょう。

ちなみに、この TIPS が適用されているリポジトリがこれです。

github.com

ルーティングルールを DFA として保持するのでルール数が増えても速いはず、というサーバーサイド用の URL ルーターです。

XAMLに思いを馳せすぎてReactにDataTemplateを輸入した

WPF、もとい MVVM 信者としては、どうしても pure な Class で ViewModel を作りたいというのがあって*1、React にその思想を持ち込んでみた。

React では往々にして Container Component といわれる MVC の Controller みたいなものと Presentation Component と言われる View みたいなものを多層に重ねて UI を作る。V と C が交互に折り重なるように見えるので、ミルフィーユ方式と呼びたい。

それに対して、MVVM(って言っていいのかな)では、ViewModel という名のただのインスタンスのオブジェクトグラフが1本あって、それとは別に View がある。で、この View は ViewModel のオブジェクトグラフとトラバーサルして変換するような形で作られる。XAML がわからない人は XSLT を想像してください。ああいう感じです(ちょっと違うけど)。

ミルフィーユ方式ではどうしても状態管理と VDOM 生成の責務がべったりしがち、ということがあってあまり好きではないんです。Controller が View を知ってる、みたいになってしまって気持ちよくない。それに対して MVVM の View はロジック(ViewModel)に対してとことん非破壊的なんですね。これがよい。ViewModel は好きなように書いて、View がそれに合わせる。理想じゃないですか。

で、VDOM のポテンシャルを活かせばそれくらいできるでしょう、と午前2時くらいの脳がひらめいたのでちょろっと書いてみました。

まず、なんの変哲もない木構造のクラスを定義します。

export abstract class Node {
  constructor(public value: string) {}
}

export class Tree extends Node {
  constructor(value: string, public children: Node[]) {
    super(value);
  }
}

export class Leaf extends Node {
}

こう、pure な感じで普通に書くわけですね。

で、それをレンダリングする View を書きます。

class TreeView extends ScopedComponent<{
  root: Node
}, {}> {
  render() {
    const {root} = this.props;

    return (
      <ul>
        <DataTemplate type={Tree}>
          {
            ({content}: { content: Tree }) =>
              <li>
                {content.value}
                <ul>
                  {
                    content.children.map((node, i) => {
                      return <ContentControl key={i} content={node} />;
                    })
                  }
                </ul>
              </li>
          }
        </DataTemplate>
        <DataTemplate type={Leaf}>
          {
            ({content}: { content: Leaf }) =>
              <li>
                {content.value}
              </li>
          }
        </DataTemplate>
        <ContentControl content={root} />
      </ul>
    );
  }
}

ナイス非破壊。これだけで再帰構造をレンダリングできます。

実装はこちらにおいてあります。XAML に恋い焦がれている方はどうぞ。PoC なので、スコープが hierarchical cascade じゃないとかで全然実用的ではないですが。

github.com

*1:C#WPF でも DependencyProperty まみれで Pure とはいえないが

GraphQLのはじめかた

トレンドに乗るためにはトレンドを自分の方に捻じ曲げるのが一番いい、ということで、Twitter で日夜 GraphQL, GraphQL と騒いでいる日々であります。

先日、tng23 で「GraphQLの話」をしてきました。解説記事とか書くみたいなことをノリで口走ったので、これは伏線回収です。

GraphQL は公式のドキュメントが充実しています。とはいえ、全部英語なので日本にはあまりリーチしてないですね。みなさん英語読んで……。

母語でない言語で書かれた文章を自分のペースで読みながら、そこに書かれている新しい概念を獲得するってのは結構な重労働です。そこで、おすすめの学び方はカンファレンスでの GraphQL に関する発表の映像を視聴することです。YouTube の自動字幕はすごくて、 "GraphQL" が "graphical" になったり "graph kill" になったりするくらいで他はめちゃくちゃ精度よく文字起こしされます。スライドもあるので言葉だけのドキュメントよりも概念の理解が楽だと思います。以下のページにたくさん動画へのリンクがあります。

graphql.org

この動画なんかは導入としてとてもいいです(作者が喋ってるので当たり前ですね)。

www.youtube.com

動画は脳にいいんですよ。

GraphQL でリアルタイムなフィードとか扱いたいです、みたいな一歩進んだ人はこれも見ましょう。

www.youtube.com

動画を見て興味が出てきたら手を動かしましょう。

express-graphql とかを使うと GraphiQL (API コンソール)付きのサーバーがサクッとできるので便利です。

github.com

Node.js で GraphQL するなら使うことになるであろう実装が graphql-js です。作者が作ってるリファレンス実装です。

github.com

半年くらい前まで結構実装に粗が目立ってたんですが、最近はコミュニティも活発でこなれてます。低レベルな API と高レベルな API がわかれてて、どちらもちゃんと export されててえらいです。えらい。

あと、flow で書かれてるので type annotation があって読みやすいですね。自分は TypeScript 派ですが。

はじめかたはこんな感じです。とりあえずはじめましょう。

PYNQ-Z1というFPGAの板を買った

PYNQ-Z1 という FPGA の板があって、それを買いました。とはいえ注文しただけで、まだ届いてない。

FedEx の高い方で輸送されるらしいので、早く届くといいな。

届いたらまたいろいろ書きます。

LINEインターンに参加した

40万円

f:id:koba789:20160904022502j:plain

LINE Payのチームでなんかの実験環境を作ったりしました。

f:id:koba789:20160902132637j:plain

例えば、これは最終日の昼に食った肉です。

f:id:koba789:20160807073851j:plain

たまたま倒壊していなかったときの建物です。

ほかにもなにかを作ったりしました。はやくリリースされるといいですね。

f:id:koba789:20160807034851j:plain

つくばから東京までは自転車で行って、五反田のベッドで寝てました。

f:id:koba789:20160825100314j:plain

これは見えるやつです。

f:id:koba789:20160826183747j:plain

夜になるとよりよい。

LINE Pay は LINE ID: koba789 にお金を送ることができる良いサービスです。

はい