クックパッド株式会社に入社しました
クックパッド株式会社に入社しました
肉焼いた
これはすこし食べた状態の肉
家で肉を焼きました。
なぜ焼いたかというと、フライパンが手に入ったからです。
フライパンというのはプログラミングで言うところの Emacs みたいなやつで、ないと肉が焼けない。
この前、友達をパシッて閉店セールやっていたつくばの西武で良いフライパンを買ってきてもらった。
間違い探しではありません。良いフライパンであることを強調するために2度貼りました。
このフライパンは良くて、どれくらい良いかと言うと、今回の肉焼くのに油を一滴も使ってない。なぜ使ってないかというとないからです。油がない。
自分で肉焼いて食うことのメリットはいくつかあって、ひとつは金。ただ、このフライパンが二食分以上の値段したのでそこは今回関係ない。もうひとつは自由で、自由に食いたい時に自分で焼く。
俺は細切れ肉を焼いて塩胡椒だけで食うのが好きで、とにかく細切れ肉を食いたい。しかし、当たり前だがそんな雑な肉を店で焼肉として提供したりはしてなくて、自分で焼く必要がある。
ちなみになんで細切れ肉かというと、表面積。肉には体積が大きいほどうまいやつと表面積が大きいほどうまいやつがあって、この場合は後者。塩胡椒というのは粘度のあるソースなんかと違って肉に張り付きづらいので味が強くなりづらい。そこで表面積。
まぁあと、細切れ肉は安い。
最最最高最技最術最責最任最者高最高高高技高術高責高任高者技最技高技技技術技責技任技者術最術高術技術術術責術任術者責最責高責技責術責責責任責者任最任高任技任術任責任任任者者最者高者技者術者責者任者者
->(c){c.product(c).flatten.join}.('最高技術責任者'.split(''))
日本語の形態素解析以外にも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 | ||
---|---|---|---|
左文脈 ID100 |
右文脈 ID300 |
左文脈 ID200 |
右文脈 ID400 |
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
最高だぞ
次回は今回の人間の知り合いの会社らしい
俺はオレンジ
お前ら行け
これは聖地渋谷までわざわざ行って撮った
インスパイアされた次回
夜の合同説明会 - アニメイトラボ, ドリコム, GMOペパボ, Repro - connpass
他の就活目的レンジャー:
就活目的、夜の合同説明会 - osyoyu.hatenablog.com
入隊したい方はこちら:
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.json
の scripts
にこれを書く。
"test": "nyc --extension .ts mocha --compilers ts:ts-node/register,babel-register"
いいですね?
ここからは CI の話
package.json
の scripts
にこれを足す。
"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
この記事を参考にした。
結果
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 が適用されているリポジトリがこれです。
ルーティングルールを 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 じゃないとかで全然実用的ではないですが。