Write and Run

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

ISUCON7「学生気分」

これは決勝出場決定チームなどと飲んだあとに酔っ払って書いている。明日は土曜日。

 

細かいことは http://osyoyu.hatenablog.com/entry/2017/10/23/014843 に書いてあると思う。この記事を読めば俺の記事は用なし。

 

8万点くらいしか取れなかったけど、かつてないチームワークを発揮し、それぞれのパフォーマンスを生かしきったいい試合だったと思うので、酔っ払いながらに書いておく。よかった。

 

git 禁止

今回で出場は3度目だが、過去二回の出場で学んだことがある。我々には運用をする奴がいない。再起動には耐えられないし、本番で ruby を動かす方法は知らない。学生はあくまでオモチャしか知らないのだ。そのため、我々にはチームワークがない。運用する奴がいないので全員がバラバラにコードを書き始める。最悪だ。

 

そこで今回、俺はある提案をした。それが git 禁止だ。

git を禁止し、あくまで本番サーバー上での編集しか許さないというルールを敷いた。このことで、コードを書く人間は一人となり、ロックを取るためにコミュニケーションが発生した。

コードを書けない人間は計測するとか便利スクリプトを揃えるとかするしかなくなる。これにより自然と分担が発生した。ちなみに俺は mysql に関する知識がミジンコ並だったのでミドリムシ程度の知識がありそうな @osyoyu に投げた。結果的に彼は MySQL 8 をインストールし損ねて環境を破壊するも、なんとか my.cnf を保守し続けてくれたので、複数の細胞からなる知的生命体であったということになった。

 

mamiya.sh

そうだね。

 

これも @osyoyu 作。3台あったマシンのうち、編集を許されていた1台目のサーバーの環境を2台目,3台目に rsync するための物体。

 

憧れの @sora_h パイセンの代表作(?)にあやかった名前をつけている。実際便利。

 

Redis 導入

よくわからないうちに redis が投入されていた。@everysick の貢献。

速くなったらしいが比較をしていないのでわからない。速そう。

 

キャッシュヒット

たぶんこれが主な敗因。

/icons/ 以下を3台それぞれのファイルシステムから nginx で配るようにしてしまった。ベンチマーカーはキャッシュ関連のヘッダを読んでくれるっぽいんだけど、3台からそれぞれやるとキャッシュヒット率が1/3。Last-Modified がね〜〜。

この辺は精進が足らない。ちなみに我々は計画的なので今回も素振りはありませんでした。精進あるのみ。

 

req_limit

/fetch に sleep を見つけた我々は、一度それを外すも、スコアにならないエンドポイントを高速化してもしゃーないことに気づいた。

しかし、ruby のスレッドを sleep で占有したくないと思った私は nginx で req_limit を差し込み、適当にゆっくりとレスポンスを返すこととした。

まぁまぁ効いた。俺作。貢献あってよかった。

 

〜〜

 

あとなんかあった。

パソコン難しいけど、チームワークは大切。git はダメ

 

 

クックパッド株式会社に入社しました

クックパッド株式会社に入社しました

株式会社はてなに入社しました - hitode909の日記

肉焼いた

f:id:koba789:20170302202840j:image

これはすこし食べた状態の肉

 

家で肉を焼きました。

なぜ焼いたかというと、フライパンが手に入ったからです。

フライパンというのはプログラミングで言うところの Emacs みたいなやつで、ないと肉が焼けない。

この前、友達をパシッて閉店セールやっていたつくばの西武で良いフライパンを買ってきてもらった。

f:id:koba789:20170302203122j:imagef:id:koba789:20170302203122j:image

 

間違い探しではありません。良いフライパンであることを強調するために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
左文脈 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 ルーターです。