Go言語にさらに入門するために係り受け解析器を書いた話

今年からGo言語に入門していますが、もう少し複雑なものをものを書いてみたいと思ったので、係り受け解析器を書きました。その過程で工夫したこと、苦労したことをまとめます。作ったものはこちら。

一人で作っているプロジェクトですが、100行以下の細かめの修正毎にPull Requestを作っているので、どのような過程でやっているかが比較的分かりやすいと思います。

easy-first algorithmについて

  • 係り受け解析アルゴリズムの1つです
  • 最も自信のある(つまり簡単な)ところから係り受け関係を付けていくgreedyな方法です
  • コーパスには正解の係り受けは付与されていますが、簡単さの順序は当然分からないので、構造化パーセプトロンでそれを含めて学習します
    • 構造化パーセプロトンについては入門エントリを書いているので、興味がある方はどうぞ

なぜ係り受け解析器を書いてGo言語の入門をするのか

  • 今年の初めからGo言語に入門していた
  • おかげでAPI wrapper的なcliツールは作れるようになった
  • しかし、以下の経験をもっと積みたい
    • もっとCPUやメモリに負荷がかかる計算をやらせたい
      • Go言語でどの辺がボトルネックになりやすいのか知りたい
      • プロファイリングで地道に改善していく経験がしたい
    • もう少し複雑なデータ構造を扱いたい
    • goroutineやchannelについての知識を、本を読むだけではなく、実装しながら掴みたい
  • 係り受け解析器を作るのは(自分にとっては)よい選択
    • ドメイン知識があるので、係り受け解析器のこと自体はそれほど心配しなくてよい
      • clojureでも実装経験済み
      • 初めてのタスクでやるとタスク固有のことではまっているのか言語のことではまっているのかの切り分けが必要
    • 特に学習部分はあまりにナイーブな実装をするとそこそこ時間もかかるし、メモリも食う

学んだこと

Goroutineによる並列処理

Go言語の定石的なところ。sync.WaitGroupを使って並列に処理をする、channelを使ってCPU数に合わせた並列度にするといったところが学べました。元の関数にはほとんど手を入れずに並列化できているので、お手軽感が分かるかと思います。

文字列連結のパフォーマンス

最初は何も考えずにfmt.Sprintfで丁寧に文字列の特徴量を作っていましたが、案の定ボトルネックになりました。読みやすさも考えて妥協できるライン(ハードコーディング)で落ち着きました。

プロファイリングの取り方

文字列連結のパフォーマンス改善をしたほうがよさそうと決めたのも、プロファイリングの結果を見てからでした。Goは標準のツールセットがよくできていますが(gofmtなど)、プロファイリングも標準のpprofで十分でした。pprofの使い方については、以下のサイトを参考にしました。

プロファイラを取りたいコードに以下のコードを仕込みます。

defer profile.Start(profile.CPUProfile).Stop()

仕込んだバイナリを実行すると、cpu.pprofができあがります。

% go build
% ./go-easy-first ...
% go tool pprof go-easy-first /var/folders/pk/zk906vvd7rx7ls8g3sv3vs800000gn/T/profile498633669/cpu.pprof

私の場合はtop 20 -cum main.list main.を実行するとボトルネックになっている箇所が大まかに分かり、そこを修正すると速度が改善できました。最初は7分程度かかっていたものが2分程度まで縮んだので、まあまあのインパクトです。また、実行する関数を文字列で取りたい場所でreflectionを使っていて、パフォーマンスが若干気になったのですが、プロファイリングを取ることでそこはほぼ問題にならない程度の時間であることが分かってよかったです。

mapの辞書引きのパフォーマンス

同じくプロファイリングを取っていて少しずつ改善していった箇所です。文字列連結のパフォーマンスを改善した後も、内積計算(DotProd)で計算時間がかかっていることが分かりました。初期は内積計算をするための重みベクトルをmap[string]float64で保持していましたが、keyが文字列で引いてくるのは明らかに時間がかかります。そこで、keyをid化して辞書引きを高速にするように変更しました。また、Feature Hashingも使い、最大の次元数を決めることで重みベクトルも[]float64となり、使用するメモリも削減&高速になりました(最大の次元数が小さすぎるとhashが頻繁に衝突してしまいますが、100万次元程度確保しておけばこのタスクではほぼ精度は落ちずに済みます)。

外部のciサービスを使う

仕事ではcircleciやtravis-ciにお世話になっていますが(mackerel-agent関係)、個人のプロジェクトでは使ったことがなかったため導入してみました。CircleCIのようなバッチをREADME.mdに付けたかったというミーハー的な動機もあります。いくつか似たようなものがありましたが、今回は以下の3つを導入しました。

ずぼらな性格なので、一人でやっているとどんどん適当になってしまいがちですが、ciを入れると適度に秩序が保てるのでよいですね。

タスクランナーとしてMakefileを使う

これまで小規模なGo言語のアプリしか使っていなかったため、go rungo buildで十分でした。しかし、後述するgo-bindata等をビルドの前に行ないたい等、色々やることが増えてくるとタスクランナーが欲しくなってきます。みんGoでも我等がid:SongmuさんがMakefileでやることをオススメしていたので、Makefileを導入することにしました。

はまったこと/困ったこと

パラメータファイルの読み書き

パラメータファイルはただのベクトル([]float64)なので、human readableなjsonである必要は特にありません。バイナリファイルに書いて、デコードのときに読み込ませるようにしようと思いましたが、イマイチbest practiceがよく分かりませんでした。試行錯誤をした結果、encoding/gobのEncoder/Decoderでやりすごしました。

この程度のことだったら、gopher的にはこの程度だったらencoding/binaryを使うのがよい?

パラメータファイルをバイナリに埋め込む

係り受け解析器を走らせるんだったら、バイナリを一個落としてくるとそれでOKな形になっているとかっこいいです。Goで書かれた形態素解析器のkagomeは実際そのようになっています。

みんGoでこういうときにはgo-bindataを使うべしとあったので、導入してみました。

しかし、ちょっと困りました。学習済みのパラメータファイルがすでにあれば普通にビルドできますが、そもそもビルドしないと学習ができないのでした。ひとまず空ディレクトリを置いとくことでビルドは通るようにしました。普通にgo-bindataを使うだけならそんなに困らないけど、機械学習のモデルもバイナリに組み込んで配布したいといった場合にはちょっと考えることが増えそうです。

副作用

ここは当てはまる人はそんなにいなさそう。前職ではClojure、現在はScalaを書いていることが多く、immutableなデータ構造を扱うことが多いのですが、Goは当然ながらそうではないので、副作用関連ではまりました。思っていなかったところが書き変わっていたり、あるいは変更できたと思っていたところが書き換えられていなかったり。immutableな場合は考えるスコープがかなり局所的ですが、mutableだと広くなりがちなので頭のスイッチに時間がかかりました。

まとめ

係り受け解析器をGo言語で書くことで学んだ様々なことについて紹介しました。慣れない言語は自分が慣れている言語と比べるとどうしても実装速度等は落ちてしまいがちですが、自分がなじんでいるドメインで実験すると新しい言語のこと自体に集中できて、なかなかよかったです。そろそろGo初心者を脱出したい…!

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

実タスクで機械学習を導入するまでの壁とその壁の突破方法

社内で機械学習の案件があった際に、機械学習の経験者しか担当できないと後々の引き継ぎで問題が起こりがちです。これを防ぐために、機械学習に興味があり、これまで機械学習を経験したことがないエンジニアにも担当できる体制を整えられることが望ましいです。しかし、機械学習のことに詳しく知らないディレクターやエンジニアにとっては、どのような機械学習の理解段階ならばタスクを任せられるかの判断をするのはなかなか困難です。そこで、このエントリでは機械学習を実タスクでやるまでに乗り越えるべき壁だと私が思っているものについて説明します。

第一の壁: 綺麗なデータで機械学習の問題を解ける

  • 講義で扱われるような綺麗なデータを扱える
    • 行列形式になっていて、欠損値や異常値もない
  • 上記のデータを回帰や分類問題として解くことができる
    • 実際に解く際にはライブラリを使って解いてよい
    • 手法を評価する上で何を行なえばよいか(PrecisionやRecallやRMSEなどを計測する)知っている
  • アルゴリズムが導出できなくてもよいが、その手法がどういう問題を解いているのかは大雑把に分かる
    • 回帰/分類だったらどういう目的関数を最適化しているか
    • その目的関数の心はどういうものか
    • 学習や予測がうまくいかないとき、どこに着目すればよいか分かる

第二の壁: 綺麗でない現実のデータで機械学習の問題を解ける

  • 整形されていないデータを機械学習で扱える特徴量の形式特徴量に落としこめる
    • 例: テキストデータをBoW形式に落とせる
    • 例: 異常値、外れ値は前処理で落とせる
  • 必要なパフォーマンスが出るようにチューニングができる
    • 特徴量選択、正則化項、頻度での足切りによるチューニング、ラベル数の偏りがある場合などへの対応
    • 「多少のゴミがあってもいいから取りこぼしが少ないようにして欲しい」「取りこぼしがあってもいいからとにかく綺麗な結果を見せたい」等の要望があったときにどうチューニングすればいいか想像が付く

第三の壁: 機械学習の問題としてどう定義し、サービスに導入していくか

  • その問題は機械学習で解ける問題なのか、そもそも機械学習で解くべき問題なのかの判断が付く
  • 機械学習で解くことが決まったとして、どのように定式化するのか
    • 例: ランキング問題として定式化するのか、分類問題の組み合わせで対処するのか
  • 学習用データや評価用データをどうやって作る/集めるか
    • そもそもどの程度学習/評価データがあればある程度安定して学習/評価できるか知っている
    • 一貫性のあるデータ作りのガイドラインを作れるか(結構難しい…)
    • 闇雲に教師データを追加しても精度は上がるとは限らない

これらの壁をどう乗り越えていくか?

  • 第二段階までの壁は機械学習に関する書籍やBlogやライブラリが最近は山のように存在するので、ハードルは相当下がっている
    • はてなの教科書もあります!
    • 逆に教材がありすぎて何からやればよいか分からない…、という悩みは最近はあると思うので、問題に合わせた最短経路を経験者が示せるとよい
    • kaggle等で未経験者と経験者で同じ問題を解き、どのような工夫をすると精度が上がるか等を一緒にやってみる
  • 第三の壁は経験によるところが大きいため、経験者がメンター的に付いてアドバイスしながら手を動かせるとよい

第四の壁: 機械学習導入後の運用

言語処理のための機械学習入門 (自然言語処理シリーズ)

言語処理のための機械学習入門 (自然言語処理シリーズ)

タスクに合わせたトークナイザ、単語分割に関連したポエム

ポエムを適当に書きます。2本立て。週末のノリなので、適当です。

Sentencepieceの紹介記事を読んだ

ニューラル言語処理向けトークナイザのSentencepieceについて書かれた紹介記事を読みました。

自分用の要約すると

  • ニューラル言語処理では語彙数が大きくなると扱いにくい
  • 単語をサブワードに分割できるものは分割して、語彙数を制限する(数千から数万)方法がよく使われる
    • 尤度を最大にするエントロピー圧縮の一部と見なせる
  • スペースもメタ文字に置き換えて生文を食わせることにより、detokenizeが言語によらず簡単になる
    • 翻訳等のタスクで助かる!
    • こういうのが必要なくなる
    • 単語分割されたものからさらに分割するわけではなく、生文からやるために計算量オーダーの削減が行なわれている
  • 従来の単語分割手法を凌ぐBLEUスコアを達成している

ということでした。ちまたで話題のneologdを使うと逆にBLEUスコアが(mecabのときよりも)落ちてしまうという実験結果もあって、応用に即した単語分割を使うべきという工藤さんの主張に(再び)納得でした。

余談ですが、生命科学系の研究所(DBCLS)でバイトした経験から、M1の初期は応用タスクに最適化された単語分割を学習できないかと思って少し考えた時期がありました。Sentencepieceのようなシンプルな手法がニューラル言語処理向けトークナイザとして役に立つのは面白いなあと、一人でテンションが上がりました。ちなみに、M1の初期のやつは、Joint Learning的なノリで解けないかと思っていましたが、挫折で終わりました…。

文書分類でneologdとmecabを比較した

「応用に即した単語分割を使うべき」というエントリを読んだのと、週末のノリで単語分割を変えてあれこれ遊びたいという欲求が湧いたので、自分のbotで試してみました。機械学習のエントリかどうかを判定してくれるbot君です。

タイトルと本文をそれぞれ形態素解析、BoWとして放り込むだけの手抜き実装です。この形態素解析の部分をneologdを使う/使わないで比較してみました。学習/テストデータの分け方で精度が変わってくるため、10回ランダムにデータを分割、F値の平均と標準偏差を出してみました。

平均 標準偏差
mecab 0.747 0.0056
neologd 0.766 0.0066

neologdを用いた場合、BoWの次元数がmecabの場合よりも増加していました。人工知能の話題でよく出てくる「東ロボくん」もneolodgでは一単語として認識されているので、このような単語により精度が上がっていそうかな。

% echo "東ロボくん" | mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd
東ロボくん      名詞,固有名詞,一般,*,*,*,東ロボくん,トウロボクン,トウロボクン
EOS
% echo "東ロボくん" | mecab
東      名詞,一般,*,*,*,*,東,ヒガシ,ヒガシ
ロボ    名詞,一般,*,*,*,*,*
くん    名詞,接尾,人名,*,*,*,くん,クン,クン
EOS

@overlastさんが過去のNL研等で報告されているように、文書分類のタスクではneologdは有効そうなことが分かりました。

まとめ

謎のテンションなので特にまとめはないですが、応用タスクによって単語分割を使い分けることを検討してみてもよさそうです。

言語処理のための機械学習入門 (自然言語処理シリーズ)

言語処理のための機械学習入門 (自然言語処理シリーズ)

NLP2017の論文を読みました

NLP2017の論文を読んだので、面白かった論文を中心に読んだメモを残しておきます。もう一月ほど前になるので、大分昔な感じがしますが…。

参考リンク集

D4-1 ニュース制作に役立つtweetの自動抽出手法

  • ○宮﨑太郎 (NHK), 鳥海心 (都市大), 武井友香, 山田一郎, 後藤淳 (NHK)
  • ニュース取材に役立つ情報をソーシャルメディアから抽出するプロジェクトがNHKにある
  • SNSから検索するためのいい感じのクエリを網羅的に用意するのは大変
  • SNSでは口語体や略語もあるので、形態素解析をせずに文字単位のRNNを作る
    • bi-directionalなRNNでベクトル表現を作ってNNでニュース取材に役立つかを判定する
    • RNNではないベースラインでは実験されていない
  • attentionとマルチタスク学習を取り入れるとさらに精度が向上した
    • マルチタスクは「ニュースが役に立つか」「言語モデル(次の文字を予測)」を2つで解く
    • 若干上がる
  • 最終的性能はF値で6割くらいでなかなか難しい

P8-7 STAIR Captions: 大規模日本語画像キャプションデータセット

  • 吉川友也 (千葉工大), ○重藤優太郎 (NAIST), 竹内彰一 (千葉工大)
  • 最近画像と言語の融合が活発、特にキャプション生成
    • 視覚障害者支援や画像検索に役に立つ
  • 画像キャプションを日本語で生成するためのデータセット
  • 構築したデータセットでNNでのキャプション生成ができることも確かめた
  • http://captions.stair.center/
    • クラウドソーシングでアノテーション
  • YJ! Captionsも類似のデータセットだが、画像数やキャプション数はこちらのほうが多い
  • MS-COCOを翻訳したものを学習データに使うよりは性能がよい
  • YJ! Captionsで学習させたものとの性能比較はどんな感じだろうか
    • よくよく考えると直接的な比較はできなかった

P7-4 抽出型文書要約における分散表現の学習―文書と要約の距離最小化―

  • ○田口雄哉, 重藤優太郎, 新保仁, 松本裕治 (NAIST)
  • 文書と要約の類似度をよりよく見れるように抽出型文書要約専用の分散表現を学習しましょう、という話
    • 教師あり学習といえば教師あり学習、直接的ではないにせよ
  • 0から学習させるわけではなく、word2vecの結果から離れないように正則化を入れる
  • 凸関数になるので、最適解はclosed-formで得られる
  • ROUGEでもまぁまぁよくなってる
  • 簡単にできるのでいい感じに見えた

B4-5 ニューラルネットワークによる日本語述語項構造解析の素性の汎化

  • ○松林優一郎, 乾健太郎 (東北大)
  • いい論文でした
  • 述語項構造は、文章内の述語とその項間の関係を規定する構造
  • 従来用いられてきた素性(統語関係パス、単語共起)を分散表現で汎化する
    • 組み合わせ素性だとsparseになるので分散表現でなましたい
    • 述語項毎に個別のルールを覚えることが重要である、ということも昔から知られており、汎化すれば性能が上がるかは自明ではない
  • 分散表現で汎化したものは二値素性と同等の精度で、二値素性も組合せると従来のものから性能が向上した
    • お互い捉えているものが異なる
    • state of the artの性能
  • 図1が大体表している
    • 統語関係パスをGRUを通して埋め込む
    • 述語・項候補の単語を埋め込む(concatする)
    • その他の二値素性
    • ガヲニ無のスコアをsoftmaxで吐く
    • 従来は5次程度の組み合わせ素性を使っていたため、多段にする

P10-5 疑似データの事前学習に基づくEncoder-decoder型日本語崩れ表記正規化

  • ○斉藤いつみ, 鈴木潤, 貞光九月, 西田京介, 齋藤邦子, 松尾義博 (NTT)
  • SNS等のくずれた日本語をどうにかしたい研究の続編
    • 崩れた分の分かち書きではなく、正規化をするタスクをやる
  • attention付きのencoder-decoder
  • 文字列正規化の正解データは多くないため、いくつかの変換パターンで疑似データを作る(表1)
    • twitterデータに対して変換をして、約11万分の疑似正解データを作った
  • Mosesでは疑似データも学習に加えるとノイズに負けて精度が落ちる
  • 提案法では疑似データも学習に加えるとノイズに負けず精度が上がる
    • とはいっても、表3によるとMosesと同じくらいの性能
    • 疑似データを増やしていくとMosesも越えられそう、という感じかな?

C2-2 係り受け構造との同時予測によるA* CCG解析

  • ○♠吉川将司, 能地宏, 松本裕治 (NAIST)
  • 優秀賞を受賞していた
  • CCGによる構文解析で曖昧性があった場合に英語ではヒューリステックを使うことである程度解決できた問題が日本語ではヒューリステックな方法では簡単には解決できない
  • 係り受け構造と同時予測することにより曖昧性を解消し、従来法と比べて高い解析精度を達成している
  • Nojiらの手法では正解の品詞情報を使っているが、提案法では品詞情報を使わず高い性能を出している

A2-1 医療テキスト解析のための事実性判定と融合した病名表現認識器

  • ○矢野憲, 若宮翔子, 荒牧英治 (NAIST)
  • 日本語の医療用NLPツールを提案する。以下の2つからなる。
    • 事象認識: 病名、疾患名を同定する
    • 陽性判定: 陽性か陰性か判定する
  • 形態素だと難しいので、文字単位で処理を行う
    • 形態素単位の実験と比較したい
    • 素性には単語ベースのものも入っている
  • 2つのタスクは関連しているので、融合(同時に解く)する
  • NTCIRの共有タスクを解く
  • 方法は固有表現抽出と同じく系列ラベリングっぽい方法で解く
  • 単語ベースのやつよりかはマージンを持ってよさそうな感じに見える
  • 方法は単純だが、医療系テキストの特徴を捉えていてよさそう

深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)

深層学習による自然言語処理 (機械学習プロフェッショナルシリーズ)

AWS Lambdaに入門する

Amazon Elasticsearch Serviceに引き続き、AWS Lambdaに入門しました。Lambdaを使って、Amazon Elasticsearch Serviceで特定の単語を検索をさせてslackに書き込んでくれるbot君を練習台でやってみました。

やりたいこと

AWS強化月間(?)ということでAmazon Elasticsearch Serviceに入門していました。

twitterやslackのデータ置き場は無事に自宅のmac miniからAmazon Elasticsearch Serviceに移行できました。しかし、肝心のクローラーは相変わらず自宅のmac miniのjenkinsが頑張っているので、それをAWS Lambdaで代わりにやってもらおうというのがやりたいことです。一気にやるのはしんどいので、今回は以下のことをやってみます。

  • CloudWatch Eventsで定期的にlambdaを起動
  • Amazon Elasticsearch Serviceに貯蓄されているtwitterのデータから特定の単語を含むものを検索
    • goを使って書いていますが、AWSの認証をどうやるか
  • 検索にヒットした発言をslackに投稿
    • lambdaから簡単に外に出られるのか

簡単なことしかやっていないですが、まぁ手始めということで。

準備: 適切なポリシーを設定する

後で説明しますが、apexからあれこれできるようにする必要があるので、IAMのポリシーを設定します。

IAMのポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:Create*",
                "iam:Attach*",
                "lambda:*"
            ],
            "Resource": "*"
        }
    ]
}

Goで書いたプログラムをapexを使いAWS Lambdaに転送

AWS Lambdaでサポートされている言語はNode.js、Java、C#、Pythonのみですが、最近練習しているGoで書きたかったので方法を探りました。

ちょっと脇道にそれますが、最近オフィスでNetatmoを使ってco2濃度をはかって、閾値を越えたら強制的に窓を開ける活動が流行っています。東京はGoogle App Scriptでやっています。

京都では同様のことをlambdaでやっている同僚がいて、聞いてみるとapexを使っているようでした。なるほど、これを使うとGoで書いてlambdaで動かせるようです。

Goでビルドしたバイナリをlambdaに送り、それを実行しているようで、外部のライブラリに依存していても割と安心なのはいいですね。サーバー上でライブラリをインストールする手間が省けます。AWSのコンソール画面をほぼ開くことなく、apex deployでlambdaにコードがdeployされていき、apex invoke hello --logsとやるとlambdaのプロセスを叩けました。1時間もかからずお手軽。

Lambda上からAmazon Elasticsearch Serviceで検索

GoからElasticsearchを叩けるようにするライブラリを作っている人はすでにいらっしゃいました。

これを使うとやりたいことができそうですが、AWSの認証を突破しないといけません。私の場合、IPでの接続制限ではなくロールでの制限をやっているので、その情報を上述のclientに渡す必要があります。AWSがgo用のsdkを用意してくれていて、それを利用した認証情報をclientに食わせると、Amazon Elasticsearch Serviceで検索できるようになりました。

あとはこれをlambda上で動かせるようにすれば完成です。githubに上げました。

ACCESS_KEYやtokenも環境変数としてlambdaに登録していく必要がありますが、以下のようにするとそこまで面倒を見てくれます。

% apex deploy --set AWS_ES_ENDPOINT=https://search-XXX.ap-northeast-1.es.amazonaws.com --set AWS_ES_ACCESS_KEY_ID=XXX --set AWS_ES_SECRET_ACCESS_KEY=YYY --set SLACK_TOKEN=xoxp-XXX --set SLACK_CHANNEL_NAME=mackerel_search
% apex invoke hello --logs < event.json

毎回invokeしたくないので、AWSのコンソール画面からトリガー(CloudWatch イベント - スケジュール: cron)を設定しました。これで数分に一回、lambda側で勝手に起動してくれます。

MackerelのAWS連携でLambdaを監視

お決まり感がありますが、Mackerelで監視させます。MackerelのAWS連携はLambdaに先日対応したので、agentを入れることなくlambdaをMackerelにホストとして登録することができます。

read_onlyなロールを作ったらすぐ連携できます、便利。

f:id:syou6162:20170331091611p:plain

まとめ

AWS lambda上でGoを使って割と自由にコードを実行することができました。今回はElasticsearchの検索しかやっていませんが、元々はクローラーを作ってElasticsearchに結果を格納していきたかったのでした。しかし、ひとまず準備は整ったかなと思います。AWSのサービスも2つ触ったことになるし、段々雰囲気が分かってきたので勉強も進みつつあると思いたいです。勉強だけだと辛いので、自分で楽しめるやつをやっていこうとは思います。

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Javaの例外をScalaで扱う

タイトルのことを調べようとしていて、三ヶ月に同じことを調べていたのでブログにメモしておきます…。主に↓に書いてある内容以上のことはありません。前より早く理解できたり、理解が深まったりしたので、進捗していると思いたい。

Scalaの場合

Scalaの関数の場合、以下のケースが多いと思います。

  • 値が存在しない場合もあるときはOption[A]を返す
    • 例: DBから値を引いてきたけど、存在しないときはNoneを返す
  • 失敗するかもしれない処理で、失敗した場合は理由も返して欲しいときはEither[A, B]を返す
    • 例: 外部との通信で破壊的な処理が失敗するかもしれないので、Left("Could not delete user")を返す
    • TrySuccessFailureというパターンもできる

SomeRightになっているもの同士の演算は取り出してくるんで…と手で丁寧にやっていると疲れるし読むのも大変です。mapflatMapforを組み合わせると書くのも楽になるし、読むのも楽になります。Haskellの本で勉強した。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

どれを使うにしてもパターンマッチでかちっとできるのは書いてても読んでても安心できますね(特にsealed traitなどを使っている場合)。

Javaの場合

ScalaのプロジェクトでもJavaのライブラリを使うことはよくよくあると思いますが、Javaの場合は失敗するかもしれない処理でもOptionEitherを返さず例外を吐くことが多いです。以下のように、一旦OptionEitherでくるむとScalaの場合と同じように扱うことができてよさそうですね。

  • allCatch opt => 例外吐いたときはNoneにして、成功した場合はSomeでくるんで返してくれる
  • allCatch either => 例外を吐いたときはLeftにして、成功した場合はRightでくるんで返してくれる

参考

RedPenで技術文書の誤りを指摘してもらおう

自然言語の誤りを指摘してくれるRedPenを手元で使えるようにしてみました、という記事です。気が向いたので、色々書いてみました。

エンジニアであっても意外と文書を書いたり見たりする機会が多い

エンジニアとしてはてなに入社後、コードレビューをする機会はもちろん多いですが、意外と自然言語(私の場合は日本語、英語がメイン)のレビューをする機会も多いことに気が付きました。他人の書いた文書に対するレビューに限らず、自分の書いた文書に対するレビューも含みます。

自然言語も機械が勝手に間違いを指摘して欲しい

プログラムでは各言語のlinterやciのテストを通すことで、レビュー前に単純な誤りに気づくことができます。typoやsyntax errorのようなささいなことを人間がやっていると疲れるので、機械が勝手にやってくれるのはいいですね。自然言語にもlinterっぽいものはいくつかあって、例えばMicrosoftのwordなどはそうでしょう。研究論文を書いてるときに簡単な誤りはwordに投げてざっとチェックして、というのをやっていました。

しかし、技術文書をwordに投げるとうまくいかないことが多いです。私の場合、markdownで文書を書く機会が多いですが、markdownのsyntaxをwordは当然ながら解釈してくれません。markdownのsyntaxに関する赤線なのか、日本語の文法としておかしいことへの赤線なのかを人間が判断していると、これまた消耗します。私はとにかく消耗したくないのです。

自然言語もルールで分かることは機械(RedPen)に指摘してもらう

自然言語もプログラミング言語と同様に文法を持ちますが、最大の違いは曖昧性の有無でしょう。自然言語処理の大部分は曖昧性をいかに扱うかとの戦いといっても過言ではありません。自然言語処理の研究分野でも最近誤り訂正の話題は活発に議論されていて、コンペティションも開かれています。Shared taskで有名なCoNLLでも2013年にテーマになりました。

100%に近い精度を出すことがまだまだ難しいため、このように研究分野になっているわけですが、ルールでも分かる簡単なものは将来と言わず今でも指摘して欲しいです。RedPenはその要望を満たしてくれるソフトウェアの1つです。

詳しい機能はサイトを見てもらうといいですが、私がよいと感じたのは以下の部分です。

  • 様々なマークアップ言語に対応している
    • markdownを含め、LaTeXにも対応
  • 日本語や英語など様々な言語に対応
  • 必要ない指摘は設定でオフにできる
    • 「誤りと指摘されたこの箇所は自分は誤りだとは思わない」というのがよくあるパターンですが、うざいと思ったら設定でオフにできます

インストールも簡単でした。

% brew install redpen

予想通り、指摘が多かったため、設定で以下の項目はオフにしました。

  • InvalidSymbol
  • KatakanaEndHyphen
  • EmptySection

指摘例

最近書いたブログの元テキストをRedPenに流してみました。私の場合、特に一文が長い表現を書いてしまう傾向があるのですが、SentenceLengthCommaNumberで怒ってくれているのはまさにそれですね。

% redpen --format markdown --conf /usr/local/Cellar/redpen/1.8.0/libexec/conf/redpen-conf-ja.xml ~/Dropbox/_posts/2017-03-20-清算用Slack-botを書いた.md 2>/dev/null
2017-03-20-清算用Slack-botを書いた.md:14: ValidationError[SentenceLength], 文長("121")が最大値 "100" を超えています。 at line: 昔のことは忘れてしまうので、清算用のSpreadsheetを作ろうとしましたが、出先で開くのは手間なので、slackからできるといいよねという妻の声がありましたが、外部でちょうどいいサーバーを持っていなかったので、そのときは流れました...。
2017-03-20-清算用Slack-botを書いた.md:14: ValidationError[CommaNumber], カンマの数 (5) が最大の "3" を超えています。 at line: 昔のことは忘れてしまうので、清算用のSpreadsheetを作ろうとしましたが、出先で開くのは手間なので、slackからできる といいよねという妻の声がありましたが、外部でちょうどいいサーバーを持っていなかったので、そのときは流れました...。
2017-03-20-清算用Slack-botを書いた.md:14: ValidationError[DoubledConjunctiveParticleGa], 一文に逆説の接続助詞 "が" が複数回使用されています。 at line: 昔のことは忘れてしまうので、清算用のSpreadsheetを作ろうとしましたが、出先で開くのは手 間なので、slackからできるといいよねという妻の声がありましたが、外部でちょうどいいサーバーを持っていなかったので、そのときは流れました...。
2017-03-20-清算用Slack-botを書いた.md:24: ValidationError[InvalidExpression], 不正な表現 "俺" がみつかりました。 at line: 俺は好きなエディタで書きたいし、gitでコード管理したいし、何ならTypeScriptで書きたいんじゃー、と思っていたところでいいエントリを見つけました。
2017-03-20-清算用Slack-botを書いた.md:29: ValidationError[InvalidExpression], 不正な表現 "最高" がみつかりました。 at line: 最高です、IDEAでリファクタリングや定義元に戻るとかもできるようになったので完璧です。

EmacsからRedPenを使う

Emacsで技術文書を書くことが多いので、Eamcs内からRedPenを叩けるとよさそうですが、すでに作っている方がいらっしゃいました。

作者の方がインストールの手間が省けるように、ディフォルトでは(作者が立てた)Herokuサーバーを見に行くようになっています。私の場合、オフラインで動いて欲しいことや社外に出すとまずい文書でも動いて欲しいということがあるため、設定でローカルのRedPenを見に行くように変更しました。

(el-get-bundle karronoli/redpen-paragraph.el)

(define-key markdown-mode-map (kbd "C-c C-r") 'redpen-paragraph)

(defvar redpen-commands
  '("redpen --format markdown --result-format json2 --conf /usr/local/Cellar/redpen/1.8.0/libexec/conf/redpen-conf-en.xml %s 2>/dev/null"
    "redpen --format markdown --result-format json2 --conf /usr/local/Cellar/redpen/1.8.0/libexec/conf/redpen-conf-ja.xml %s 2>/dev/null"))

(defvar redpen-paragraph-force-reading-whole t)

これでC-c C-rで校正の結果が出てきて、編集すべき箇所にすぐに飛べるようになったので、快適になりました。

まとめ

機械で指摘してくれる箇所は機械にやってもらって、人間はもっと本質的なところを考える時間を増やしていきたいですね。

理科系の作文技術 (中公新書 (624))

理科系の作文技術 (中公新書 (624))