焼肉が食べたい

ただの日記です。技術的に学んだことも書こうと思っていますが、あくまで自分用メモです。 プロフィールはこちら。https://chie8842.github.io/aboutme/

去年の振り返りと今年がんばること

こういうの書いたことなかったけど気が向いたので書いてみました!とはいえ昨日書き終わらなくて年明けてしまった!

去年の振り返り

去年の振り返りをかきます!

転職

Googleに転職しました。 スカウトは3,4年前からくることがあったけど受けたことはなくて、ちょうどいいタイミングで興味のあるポジションを紹介してもらったので受けてみてました。 Web企業での開発も楽しくてそういう仕事を続けたい気持ちも残っていたのでオファーもらったあともけっこう悩んだけど、転職しました。 GCPのツールを作ったりPoCをしたりするチームです。 12月入社なのでまだキャッチアップ中の状況ですが、上司も同僚もとても親切だし環境もよくごはんがおいしいので、今のところとても楽しいです。はやく貢献できるようにがんばりたいです。

前職でのしごと

去年は主に既存サービス向けの検索改善や推薦システム構築と、新規事業向けの検索システム構築をやっていました。KPIを考えたり色んな人を巻き込んだりと、主にビジネス面が鍛えられたこと、またElasticSearchやSolrのコードを読んで内部構造を勉強できたので楽しかったです。 あとはブリストルに出張してグローバルチームとの開発もやりました。

Publication

海外登壇2回、国内登壇3回、オーガナイズ3回、ブログ記事は1件行いました。 アウトプットすると人からいろいろなフィードバックや新しい情報を得られるので楽しくやっていました。 転職したので来年は登壇の機会は減りそうだけど、興味のある勉強会には積極的に参加していきたいです。

Kaggle

3月にPetFinderに1週間だけチャレンジして、PrivateLB18位で銀メダル(トップ1%)になりました。ただシェイクがあったので運が良かった部分もあると思います。

競技プログラミング

今までやったことがなかったのですが、流行っているのでAtCoderの問題を解いてみたりしました。転職のためにはじめたわけではなかったですが、そのあとコーディング面接の練習にも使いました。ランクが上の人の解き方を見ると、あー、こういう解き方があるのか、とか、こんなに短く書けるのか、とか感動しました。折を見て次はコンテストで上のランクを目指す挑戦もしてみたいです。

ISUCON

@takegue、@himktと一緒にはじめてでてみたけど大敗しましたorz チーム練習をしてあーだこーだ考えたりして楽しかったです。 ちゃんと準備して出られる機会があったらまたチャレンジしてみたいです。

資格

転職に伴ってGCPのProfessional Data Engineerの資格をとりました。 Hadoopとかビッグデータの知識があれば解ける感じの問題なので、特に勉強しなくてもとれました。でも「資格とって」、と言われた2日後にとったのでびっくりされました。(勢い)

プログラミング言語

Rustとgoを勉強しました。個人的にはRustが楽しいなぁと思いました。 今の仕事ではgoを使う機会のほうが多そうです。

OSS

Publicに公開するようなものは対して作ってなかったです。 Contributionとしては、TensorFlowやgensimにちまっとしたPRを投げたり、TensorFlowのドキュメント翻訳をやっていました。 強いて言えばTensorFlowドキュメント翻訳のCIツールは主開発者として作りました。

github.com

英語

例年どおり、勉強したりしなかったりを繰り返していました。 英語がペラペラの@ayemos におしえてもらったカランメソッドや、TOEIC English Upgrader、BBC 6minute Englishなどのスマホアプリを使ってシャドーイングをしていました。しかしなかなか上達しなくて凹みました。。

しごと以外

仕事以外のイベントもまとめてみます!

引っ越し

2年に一度ほど引っ越しをする習性があるのですが、今回は初めて便利屋の友だちを呼んで軽トラで引っ越すをやってみました。 自分でやるといろいろ大変で、引っ越し屋さんの偉大さがわかりました。笑

スポーツ

2週間に1回くらい、朝7時からスポル大井町で友達と早朝テニスをやっています。これは来年も継続したい。 健康のためにはストレッチや筋トレも毎日やりたいけれど、やったりやらなかったりでした。あとは毎朝なぜか朝起きたら壁で逆立ちする習慣があります。

旅行

去年は海外出張が多かったのもあり、あまり遠くには出かけなかったです。 前職で同僚とスノボやツーリングにいったり、一人で別府温泉にいって積ん読消化していたりしました。

電子工作にチャレンジした

前職でハードウェアエンジニアの席が近くて、興味が湧いて自分も3Dプリンタ買ってCADを使って小物をつくったり、M5Stackで窓の防犯ブザーをつくったりしました。 Mint60の足を作ったら売って欲しいという声があって、boothで売ったら3Dプリンタ代の半分くらいの儲けがありました。

f:id:chie8842:20200101155851j:plainf:id:chie8842:20200101155916j:plain

photos.app.goo.gl

また、前職で同僚たちと一緒にNANDだけで24時間耐久全加算器を作る会をやったのがエモくて楽しかったです。またやりたいなぁ。。
f:id:chie8842:20200101160320j:plain

でけたー😃ゼロから始める24時間耐久電子工作大会終了です。おやすみなさいー pic.twitter.com/Go7be89J4b

— ち (@chie8842) November 17, 2019

投資の勉強をした

いちどちゃんと勉強したほうがいいだろうと思ったけどお金は使う方も貯める方も意識が低く、全然勉強してきませんでした。 老後に対する不安があるので一念発起して証券口座を作って実際に投資をしてみました。 えいっと買った株が2ヶ月放置しただけで1.5倍になってたので才能あるかもしれません!(フラグ)

今年がんばること

今年頑張ることをかきます!

勉強

ここ3年ほどは、推薦、データエンジニア、機械学習、インフラ、検索とわりとその場で求められることをやってきたので、エンジニアとして幅を広げたりビジネス感覚とのバランスがとれるようになってきたできた気がしますが一方で、全部中途半端でよくない気もしています。今の仕事ではデータエンジニアリングを主に担当するので、そのへんの知識をもう一度勉強するのと、それに伴って低レイヤーの勉強をしたいです。 あとビジネススキルも伸ばしたいです。

早寝早起き

前職では朝起きれないキャラだったのですが、転職してから朝ごはん目当てで会社に9:30に着いています。 せっかくなので継続して朝強いキャラになりたいです。

英語

今まで英語でコミュニケーションをとれるようになりたいと思うものの英語の勉強に苦手意識があり、そこまで仕事上必要性が高くなかったこともあって何度も挫折しています。 転職して英語の必要性が格段にあがったので、今年は英語ペラペラになりたい。

体を動かす

わたしは肺が弱くて風邪を引くとすぐ肺炎になったり発作が出たりするので、意識的に健康でいられるように体は動かすようにしています。継続的に運動するぞ!

アンテナを張りつづける

いろんなことにアンテナを張って生存戦略考えて生きていきたいです。エンジニアとしてもそれ以外の面でも。

Elasticsearch7.2でkuromoji_ipadic_neologd_tokenizerのsearchモードとsynonym_token_filterを一緒に使うとエラーが出る

TL;DR

Elasticsearch7.2でKuromoji IPADic Neologd TokenizerのsearchモードとSynonym Token Filterを使うとエラーが出る。
エラーを回避するには、Synonym Token Filterを利用するanalyzerではkuromoji_tokenizerのnormalモードを使う

発生した問題

ElasticsearchでKuromoji IPADic Neologd TokenizerのsearchモードとSynonym Token Filterを一緒に使ったところ、インデックス作成時にエラーが出た。

/var/log/elasticsearch/[cluster name].logのエラー

エラーメッセージ(クリックして表示)

[2019-09-19T05:57:48,919][DEBUG][o.e.a.a.i.t.p.TransportPutIndexTemplateAction] [n01] failed to put template [shops_template]
java.lang.IllegalArgumentException: failed to build synonyms
        at org.elasticsearch.analysis.common.SynonymTokenFilterFactory.buildSynonyms(SynonymTokenFilterFactory.java:138) ~[?:?]
        at org.elasticsearch.analysis.common.SynonymTokenFilterFactory.getChainAwareTokenFilterFactory(SynonymTokenFilterFactory.java:90) ~[?:?]
        at org.elasticsearch.index.analysis.AnalyzerComponents.createComponents(AnalyzerComponents.java:84) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.analysis.CustomAnalyzerProvider.create(CustomAnalyzerProvider.java:63) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.analysis.CustomAnalyzerProvider.build(CustomAnalyzerProvider.java:50) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.analysis.AnalysisRegistry.produceAnalyzer(AnalysisRegistry.java:584) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.analysis.AnalysisRegistry.build(AnalysisRegistry.java:534) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.analysis.AnalysisRegistry.build(AnalysisRegistry.java:216) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.IndexService.<init>(IndexService.java:180) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.index.IndexModule.newIndexService(IndexModule.java:411) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.indices.IndicesService.createIndexService(IndicesService.java:563) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.indices.IndicesService.createIndex(IndicesService.java:512) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService.validateAndAddTemplate(MetaDataIndexTemplateService.java:235) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService.access$300(MetaDataIndexTemplateService.java:65) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.metadata.MetaDataIndexTemplateService$2.execute(MetaDataIndexTemplateService.java:176) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:47) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.MasterService.executeTasks(MasterService.java:687) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.MasterService.calculateTaskOutputs(MasterService.java:310) ~[elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.MasterService.runTasks(MasterService.java:210) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.MasterService$Batcher.run(MasterService.java:142) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:150) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:188) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:688) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedEsThreadPoolExecutor.java:252) [elasticsearch-7.3.2.jar:7.3.2]
        at org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:215) [elasticsearch-7.3.2.jar:7.3.2]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at java.lang.Thread.run(Thread.java:835) [?:?]
Caused by: java.text.ParseException: Invalid synonym rule at line 10
        at org.apache.lucene.analysis.synonym.SolrSynonymParser.parse(SolrSynonymParser.java:72) ~[lucene-analyzers-common-8.1.0.jar:8.1.0 dbe5ed0b2f17677ca6c904ebae919363f2d36a0a - ishan - 2019-05-09 19:35:41]
        at org.elasticsearch.analysis.common.SynonymTokenFilterFactory.buildSynonyms(SynonymTokenFilterFactory.java:134) ~[?:?]
        ... 27 more
Caused by: java.lang.IllegalArgumentException: term: 焼餃子 analyzed to a token (焼餃子) with position increment != 1 (got: 0)
        at org.apache.lucene.analysis.synonym.SynonymMap$Parser.analyze(SynonymMap.java:325) ~[lucene-analyzers-common-8.1.0.jar:8.1.0 dbe5ed0b2f17677ca6c904ebae919363f2d36a0a - ishan - 2019-05-09 19:35:41]
        at org.elasticsearch.analysis.common.ESSolrSynonymParser.analyze(ESSolrSynonymParser.java:57) ~[?:?]
        at org.apache.lucene.analysis.synonym.SolrSynonymParser.addInternal(SolrSynonymParser.java:114) ~[lucene-analyzers-common-8.1.0.jar:8.1.0 dbe5ed0b2f17677ca6c904ebae919363f2d36a0a - ishan - 2019-05-09 19:35:41]
        at org.apache.lucene.analysis.synonym.SolrSynonymParser.parse(SolrSynonymParser.java:70) ~[lucene-analyzers-common-8.1.0.jar:8.1.0 dbe5ed0b2f17677ca6c904ebae919363f2d36a0a - ishan - 2019-05-09 19:35:41]
        at org.elasticsearch.analysis.common.SynonymTokenFilterFactory.buildSynonyms(SynonymTokenFilterFactory.java:134) ~[?:?]
        ... 27 more
[2019-09-19T05:58:00,832][INFO ][o.e.c.m.MetaDataCreateIndexService] [n01] [shops] creating index, cause [auto(bulk api)], templates [], shards [1]/[1], mappings []
[2019-09-19T05:58:00,983][INFO ][o.e.c.m.MetaDataMappingService] [n01] [shops/2DRfIkXNS66azf7EEvGuZw] create_mapping [_doc]

ちなみにエラーが出たときのAnalyzerとmapの設定はこれ。

  "settings": {
    (省略)
    "analysis": {
      "filter": {
        "my_synonym": {
          "type": "synonym",
          "synonyms_path": "synonym.txt"
        }
      },
      "tokenizer": {
        "kuromoji_search": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "search"
        }
      },
      "analyzer": {
        "synonym_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_search",
          ...
          "filter": ["my_synonym", ...]
        },

原因調査してわかったこと

ログに以下のエラーがあることから、synonym.txtの「焼餃子」を含む行の構文に問題があるらしい。

Caused by: java.lang.IllegalArgumentException: term: 焼餃子 analyzed to a token (焼餃子) with position increment != 1 (got: 0)

ちなみにsynonym.txtの中身はこんなかんじ

...省略...
焼き餃子, 焼きぎょうざ, 焼餃子

ここで、「焼餃子」をsynonym_analyzerでtokenizeした結果を見てみる。

  "settings": {
    (省略)
    "analysis": {
      "tokenizer": {
        "kuromoji_search": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "search"
        }
      },
      "analyzer": {
        "kuromoji_search_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_search",
          
        },

Analyzeした結果

$ curl -XPOST 'http://localhost:9200/hoge_index/_analyze?pretty' -H "Content-type: application/json" -d '{"analyzer": "kuromoji_search_analyzer", "text": "焼餃子"}'
{
  "tokens" : [
    {
      "token" : "焼",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "焼餃子",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0,
      "positionLength" : 2
    },
    {
      "token" : "餃子",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

エラーメッセージにあった、position increment != 1 (got: 0)positionっぽいものがある。 Analyzerのtokenizerにkuromoji_ipadic_neologd_tokenizersearchモードを利用すると、synonymのanalyzeの結果として、複合語を考慮した出力がされるが、「焼」と「焼餃子」でpositionの値が一緒であるためにpositionのincrementのエラーが出ているっぽい。 ソースコードを追っていってもその様子はけっこう容易に理解できた。 ただ、positionのcheckはlucene側に実装があったが、なんのためのチェックかはコメントなどもなくわからなかった。

次に過去のIssueを調べた結果、以下のIssueコメントを見つけた。

Ensure TokenFilters only produce single tokens when parsing synonyms by romseygeek · Pull Request #34331 · elastic/elasticsearch · GitHub

synonymを使いたいときは、normalモードを使えとのこと。 そのとおりにしたらうまくいった。

一応試しにnormalモードのAnalyzerを使って「焼餃子」Analyzeしてみる。

Analyzer

    "analysis": {
      "tokenizer": {
        "kuromoji_normal": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "normal"
        }
      },
      "analyzer": {
        "kuromoji_normal_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_normal"
        }
       ...

Analyzeした結果

$ curl -XPOST 'http://localhost:9200/hoge_index/_analyze?pretty' -H "Content-type: application/json" -d '{"analyzer": "kuromoji_normal_analyzer", "text": "焼餃子"}'
{
  "tokens" : [
    {
      "token" : "焼",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "餃子",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

normalモードだと複合語である「焼餃子」が消えて同じpositionをとるtokenがなくなっていた。

最終的な構成

normalモードで動くとはいえ、複合語も考慮したインデキシングはしてほしいので、最終的には以下のようにした。

Analyzerの構成

    "analysis": {
      "filter": {
        "my_synonym": {
          "type": "synonym",
          "synonyms_path": "synonym.txt"
        }
      },
      "tokenizer": {
        "kuromoji_normal": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "normal",
          "discard_punctuation": "true",
          "user_dictionary": "userdict.txt"
        },
        "kuromoji_search": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "normal",
          "discard_punctuation": "true",
          "user_dictionary": "userdict.txt"
        }
      },
      "analyzer": {
        "kuromoji_search_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_search",
          "char_filter": ["kuromoji_iteration_mark"],
          "filter": [
            "lowercase",
            "cjk_width",
            "kuromoji_baseform",
            "kuromoji_part_of_speech"
          ]
        }
        "synonym_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_normal"
          "char_filter": ["kuromoji_iteration_mark"],
          "filter": [
            "lowercase",
            "cjk_width",
            "kuromoji_baseform",
            "kuromoji_part_of_speech",
            "my_synonym"
          ]
        },
       ...
      }
    }

mapping

...
    "properties": {
      "hogehoge": {
        "type": "text",
        "fields": {
          "kuromoji_field": {
            "type": "text",
            "analyzer": "kuromoji_search_analyzer"
          },
          "synonym_field": {
            "type": "text",
            "analyzer": "synonym_analyzer"
          },
          ....
        }
      },
...

query

curl -XGET 'http://localhost:9200/hoge_index/_search?pretty' -H 'Content-Type: application/json' -d'{
  "query": {
    "multi_match" : {
      "type" : "cross_fields",
      "query" : "焼餃子",
      "fields" : ["hogehoge.kuromoji_field^5", "hogehoge.synonym_field"],
      "operator" : "and"
    }
  }
}'

その他試したこと1

www.elastic.co

上記を見ると、The tokenizer parameter controls the tokenizers that will be used to tokenize the synonymとあるので、以下のようにsynonym filterのパラメータにtokenizerを渡せばsynonymのTokenizerだけnormalモードを使ってその他の単語のTokenizeはsearchモードを使うことができるのかと思ったけど、最初と同様のエラーがでてできなかった。

    "analysis": {
      "filter": {
        "ja_synonym": {
          "type": "synonym",
          "synonyms_path": "synonym.txt",
          "tokenizer": "kuromoji_normal"
        }
      },
      "tokenizer": {
        "kuromoji_normal": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "normal"
        },
        "kuromoji_search": {
          "type": "kuromoji_ipadic_neologd_tokenizer",
          "mode": "search"
        }
      },
      "analyzer": {
        "kuromoji_search_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_normal",
          "filter": ["ja_synonym"]
        }

その他試したこと2

ちなみに、このブログを見ると、

Synonym Token Filter を日本語と一緒に使用する場合は、辞書の内容もトークナイザーの仕様にあわせて単語を分割して登録しておく必要があります。

とあるが、このやり方だとインデックス作成時はエラーが出ないが、一つの単語として認識してくれないのでシノニムとしては使えなかった。 Elasticsearch5.3あたりのドキュメントを見ると、The tokenizer parameter controls the tokenizers that will be used to tokenize the synonym, and defaults to the whitespace tokenizer. とあるので、こちらのバージョンでの話なのかもしれない。

Web+DB Press Vo.104の特集1を執筆しました!

Web+DB Press Vo.104の特集1を執筆しました。わたしにとっては人生初の執筆で、学ぶことも多かったので、せっかくだからブログに書いてみます。

gihyo.jp

構成

章構成は共著者で話して決めました。 1つの特集で初めてさんでもPythonのインストールから機械学習モデルをつくってアプリケーション化するところまでできる構成になっています。

タイトル:イマドキPython入門
第1章:Pythonはどんな言語?
広く使われる理由を歴史とともに掘り下げる……@puhitaku
第2章:開発環境を整えよう
はまらない導入手順と開発ツールの使い方……@puhitaku
第3章:文法を押さえよう
基本的な書き方とPython特有の機能……@chie8842,@rhoboro
第4章:科学技術計算と機械学習をやってみよう
NumPy,SciPy,pandas,scikit-learn……@chie8842
第5章:Webアプリケーションを作ろう
Bottleによるアワビの年齢推定アプリの開発……@rhoboro

わたしはこのうちの、3章(共著)と4章の2つを担当させていただきました。 1つの特集でこれだけの内容を盛り込めたのは結構頑張ったのではないかと思います!

執筆経緯と執筆手順

きっかけは、PyCon JPの登壇でした。登壇資料を見た執筆関連者を通じて、一緒に取材を受けた共著の @puhitaku さんから声をかけていただきました。

執筆はすべて、Issue管理や校正含め、作業はほとんどGitHub上でを用いて行いました。markdownで記述でき、執筆ルールも編集側でわかりやすくまとめてくださっていたので、 作業はとてもやりやすかったです。 編集者さんといえばアナログなイメージを持っていたので、担当してくださった@inaoさんが、エンジニアでないのにGitHubを使いこなしていてびっくりしました。

執筆手順は以下でした。

  1. アウトラインを決める
  2. 章ごとにブランチを切ってそれぞれ執筆
  3. 相互レビュー
  4. 編集者の方と一緒に校正
  5. 外部レビュワーによるレビュー

(3~5は何回か繰り返した)

最後の方になると、PDFの見本が出来上がってきて、DropBox上でレビューを行いました。初めてDropBoxのPDF書き込み機能を使いましたが、大人数のレビューもやりやすかったです。 また、個人的に校正ツールとして、RedPenを使いました。

ためになったこと

Pythonについてより深く学ぶことができた

記事を執筆するにあたり、各ライブラリの公式ドキュメントやソースコードを改めて読み返しました。最新の機能や内部的挙動など、わかったつもりだったけれど執筆を通して改めて気づいた点も多々ありました。

また、共著の二人はそれぞれ少しずつ違ったバックグラウンドを持っていたので、相互レビューを通して学んだ点も多くありました。

共著の大変さを知った

執筆前は、共著だと単純に書く量が減るからその分楽だと思っていましたが、全然そうじゃなかったです。笑 執筆速度や、どの時点でどのような品質を目指すか、というところの目安が人によって違います。書き方のクセも違います。なので、各々が少しずつ人に合わせて書く必要がありました。

また、活動場所や活動時間が違いました。私は個人活動としてやっていたから、基本平日は夜遅くしか対応できなかったですが、平日の昼に書くメンバもいました。また、全員別の会社の所属なので、例えば相互レビューをしているときに、気軽に「ここどういう意味?」とか聞くようなことができないという難しさがありました。

こうしたことから、相互レビューは大変で、時間も神経も執筆の何十倍も使いました。

進捗に関して@inaoさんが適切なタイミングでプッシュしてくれましたし、共著の二人とも非常に文章力技術力ともに高かったので、本当に助けられました。

外部レビュワーという存在

今回外部レビュワーとして、@takanoryさんと@terapyonさん、@yutailang0119 さんがレビューに加わっていただき、相互レビューで気づかないいろんな観点での指摘をもらうことができました。 執筆にあたり、「外部レビュワーにレビューしてもらう」ということを知らなかったので、実は様々な技術書がこうした方々に支えられているんだと知りました。

うれしかったこと

Amazonの著者に自分の名前が!

なんだか大物になった気持ちになりました。笑

WEB+DB PRESS Vol.104

WEB+DB PRESS Vol.104

  • 作者: 末田卓巳,林田千瑛,陶山嶺,八谷賢,辰己佳祐,竹澤俊季,服部智,藤岡裕吾,牧大輔,西郡卓矢,松木雅幸,穴井宏幸,新日出海,桑原仁雄,小田知央,ひげぽん,池田拓司,はまちや2,竹原,大場光一郎,大場寧子,松館大輝,日高尚美,Vu Xuan Dung,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/04/24
  • メディア: 単行本
  • この商品を含むブログを見る

自分の落書きがすごくかわいいイラストになった

イラストのデザインとか、キャッチコピーも自分たちで考えたのですが、 イラストは私の落書きをコンセプトとして採用してもらい、落書きから想像できないほどすごくかわいいイラストに変身していて、感動しました。

おわりに

執筆したWeb+DB Vol.104は私のもとにも献本いただけたので、他の章も読んでみましたが、他にも興味深い内容が多かったです。特集2のiPhoneアプリ開発など、ちょうどモバイルアプリも勉強したかったので、楽しく読めました。 まだ買っていない皆様、ぜひ買って読んでください!

時間のかかるデータ処理や機械学習処理が終わったら通知してくれるツールをつくった!

Pythonのメソッドにデコレータをつけるだけで、その処理が終わったらSlackやメールや画面のポップアップで通知してくれるツールを作りました。

github.com

正直そんなに大したツールではないんだけど、 こういう時間のかかる試行錯誤をサポートしてくれるツールを便利だと思ってくれる人が他にもいたらうれしいな、と思うのでブログに書いてみます!

作った背景とコンセプト

わたしは普段、機械学習や、大量データに対するデータ処理や分析をやっています。 いつも時間がかかるスクリプトを回しながら他の仕事をして、定期的にスクリプトが終わったか確認するということをしていたのですが、 これが面倒なので、処理が終わったら通知してくれると便利だなと思ったのが作るきっかけです。

ちなみに、kerasやtensorflow、chainerなどを使う場合だと、以下のツールがあります。

Hyperdash | Machine Learning Monitoring

Hyperdashは、フレームワークからメタデータを取得してスマ―トフォンで機械学習の詳細な進捗具合を見ることができるなど多機能です。 これはすごく便利なのですが、限られディープラーニングフレームワークでしか使えません。 また、利用するフレームワークによって実装方法が異なり使い方が少し複雑です。

わたしはディープラーニング以外に、PySparkを使った分析や、SQLAlchemyを使ってRedShiftに接続してPythonからSQLを実行するということをやることも多いです。 なので、いろいろな場面で使いやすいものがほしいと思い、

  • 汎用的に使えてかつ使い方が簡単
  • 終わったら通知するだけのシンプルな仕様

をコンセプトとして作りました。

使い方

使い方はめっちゃ簡単で、pipでインストールして、configファイルに数行の設定を書いて、処理が終わったら通知してほしいメソッドの上にデコレータをかくだけです。

インストール

pip install easy_notifier

デコレータの記述

@easy_notifier(~/.easy_notifier.cfg)
def test_method()
    ・・・
     処理の中身
    ・・・

test_method()

通知の結果はこんなかんじです。

Slackでの通知

メールでの通知

macのポップアップでの通知

通知の中のreturnはreturn valueで、返り値を表示します。 今見るとわかりにくいのであとで修正しておこう。。。

設定ファイルの中身はこういう感じです。 必要なところだけ埋めて使います。

[easy_notifier]
env = ec2
process_name = test_process
notify_slack = true
notify_gmail = true
notify_mac = false
incoming_webhook_url = 'https://slack/testurl'
slack_id = my_slack_id
channel = my_slack_channel
from_address = test@gmail.com
from_password = password
to_address = test2@xxx.com

以下は各パラメータの説明です。

パラメータ 説明
env 上の通知のinstance_nameの取得方法で、ec2、local、gceのいずれかを指定します。EC2の場合はNameタグをinstance_nameとして利用します。localの場合はhostnameがinatance_nameとなります。gceはまだ実装していなくて、hostnameがinstance_nameとなります。
process_name 上の通知のprocess_nameの表示内容を指定します。何も指定しない場合は、デコレータをセットした対象のメソッド名が利用されます。
notify_[slack|mac|gmail] それぞれ、slack、macのポップアップ、gmailからのメール通知を使うかどうかを設定します。trueもしくはyesの場合、その通知方法での通知が行われます。macのポップアップでの通知をLinuxWindows上で指定した場合は無視されます。
incoming_webhook_url slackでの通知を行う場合は、slack上で通知を受け取るためにincoming_webhookの設定を行い、そのURLをこのパラメータに設定する必要があります。
slack_id ここに、slackのDisplay nameを設定すると、その人にメンションされます。
channel slackで通知してほしいチャンネルを設定します。
from_[address|password] gmailからのメール通知を行う際の通知元のgmailアドレスとパスワードを設定します。
to_address 通知先の任意のメールアドレスを指定します。

あとTODOとして、jupyter notebookのマジックコマンドでも同じことできるようなの作ったら便利かなと思っています。 以上です。 もし使ってやってもいいよという人がいたら、感想とか教えてもらえたらとてもうれしいです!

p3インスタンス(V100)上でCUDA+CUDNN+Tensorflowを動かすのが大変だったのできろく。

p3で機械学習基盤を作ったのだけれど、いろいろややこしかったのでメモしておく。
ただしこれは2017/11/27現在の状況であって、またそのうち移り変わると思う。

環境

OS: Ubuntu

NVIDIA-DRIVERインストール

普通はこれで入る。

sudo apt-get install nvidia-384

でも、確認すると、これでインストールされるのは、nvidia-384.90
nvidiaのページを見ると、nvidia-384.90はv100をサポートしてない。

NVIDIA DRIVERS Linux x64 (AMD64/EM64T) Display Driver

それで、ちゃんとNVIDIAのホームページからvoltaに対応しているnvidia-384.81をインストールすればいいと思った。

でも実はこれじゃだめだった。
理由はあとで説明する。

CUDAインストール

volta自体は、cuda Toolkit version8.0以前を使って構築されたアプリケーションも、
条件付きで動かすことはできるらしい。

Volta Compatibility Guide :: CUDA Toolkit Documentation

が、面倒くさそう。
あと、cudnn7はcuda8.0だとうまく動かない場合があるらしい。

cuda9にする。
インストールの形式は、
Linux -> x86_64 -> Ubuntu -> 16.04 -> runfile(local)
を選ぶ。
ここでdeb(local)やdeb(network)でインストールをすると、
nvidia-driverのバージョンが384.90にアップデートされてしまう。
nvidia-driverのインストールのところでの「これじゃだめだった。」の理由はこれ。
nvidia-driver単体のインストールは行わず、runfileでインストールすると、
nvidia-driver384.81とcuda9.0のコンビでインストールしてくれる。
f:id:chie8842:20171127191626p:plain

ちなみにcuda8だとこれをうまくやってくれるインストール形式がない。

結局のところのnvidia-driverとcudaのインストール手順は以下。

sudo apt-get update
wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda_9.0.176_384.81_linux-run
sudo apt-get install -y gcc && sudo apt-get install -y make
sudo sh cuda_9.0.176_384.81_linux-run

nvidia-smiでGPUの動作状況を確認する。

$ nvidia-smi

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   35C    P0    35W / 300W |      0MiB / 16152MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

ついでにPersistence ModeをONにしておく。

$ sudo nvidia-smi -pm 1
Enabled persistence mode for GPU 00000000:00:1E.0.
All done.
$ nvidia-smi
Mon Nov 27 13:19:38 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-SXM2...  On   | 00000000:00:1E.0 Off |                    0 |
| N/A   37C    P0    21W / 300W |     10MiB / 16152MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

cudnnインストール

インストールパッケージ一覧を見ると、cuda9の場合、cudnnのバージョンは7.0.3しかでてこなかったので、cudnn7.0.3をインストールする。
f:id:chie8842:20171127193028p:plain

cudnnのインストールは、tgzファイルをサーバに配置して、以下を実行すればいい。

mkdir -p /usr/local/cuda/lib64
mkdir -p /usr/local/cuda/include
tar xzvf cudnn-9.0-linux-x64-v7.tgz
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/
sudo cp cuda/include/cudnn* /usr/local/cuda/include/
sudo ln -sfv /usr/local/cuda/lib64/libcudnn.so.7.0.3 /usr/local/cuda/lib64/libcudnn.so.7

tensorflowインストール

ここまでやって、普通にtensorflowのexampleを動かしてみたけど、
エラーが出る。

apt-get install python3-pip
pip3 install tensorflow-gpu
python3 $HOME/tensorflow/tensorflow/examples/tutorials/mnist/mnist_deep.py
# ImportError: libcublas.so.8.0: cannot open shared object file: No such file or directory

この辺のIssueを見ると、ソースコードをr1.4のソースコードをビルドしないと動かないっぽい。

Upgrade to CuDNN 7 and CUDA 9 · Issue #12052 · tensorflow/tensorflow · GitHub CUDA 9RC + cuDNN7 · Issue #12474 · tensorflow/tensorflow · GitHub

Tensorflowのドキュメントを参考に、ソースコードからビルドする。

すでにtensorflow-gpuをインストールしている場合は、アンインストールする。

sudo pip3 uninstall tensorflow-gpu tensorflow-tenworboard

依存パッケージをいろいろインストール

sudo apt-get -y install python-numpy python-dev python-pip python-wheel
sudo apt-get -y install python3-numpy python3-dev python3-pip python3-wheel
sudo apt-get -y install libcupti-dev

bazel(ビルドツール)インストール

sudo apt-get -y install openjdk-8-jdk
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update && sudo apt-get install oracle-java8-installer
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
sudo apt-get update && sudo apt-get install bazel
sudo apt-get upgrade bazel

cuda9+cudnn7対応が行われているv1.4.0のタグを指定してtensorflowをgit clone

git clone -b v1.4.0 https://github.com/tensorflow/tensorflow
cd tensorflow

ビルドのオプション設定を行う。
cuda以外のオプションはデフォルトにした。

$ ./configure
You have bazel 0.7.0 installed.
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python3.5


Found possible Python library paths:
  /usr/local/lib/python3.5/dist-packages
  /usr/lib/python3/dist-packages
Please input the desired Python library path to use.  Default is [/usr/local/lib/python3.5/dist-packages]

Do you wish to build TensorFlow with jemalloc as malloc support? [Y/n]: 
jemalloc as malloc support will be enabled for TensorFlow.

Do you wish to build TensorFlow with Google Cloud Platform support? [Y/n]: 
Google Cloud Platform support will be enabled for TensorFlow.

Do you wish to build TensorFlow with Hadoop File System support? [Y/n]: 
Hadoop File System support will be enabled for TensorFlow.

Do you wish to build TensorFlow with Amazon S3 File System support? [Y/n]: 
Amazon S3 File System support will be enabled for TensorFlow.

Do you wish to build TensorFlow with XLA JIT support? [y/N]: 
XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with GDR support? [y/N]: 
No GDR support will be enabled for TensorFlow.

Do you wish to build TensorFlow with VERBS support? [y/N]: 
VERBS support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: 
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: y
CUDA support will be enabled for TensorFlow.

Please specify the CUDA SDK version you want to use, e.g. 7.0. [Leave empty to default to CUDA 8.0]: 7.0.3


Please specify the location where CUDA 7.0.3 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:


Invalid path to CUDA 7.0.3 toolkit. /usr/local/cuda/lib64/libcudart.so.7.0.3 cannot be found
Please specify the CUDA SDK version you want to use, e.g. 7.0. [Leave empty to default to CUDA 8.0]: 9.0


Please specify the location where CUDA 9.0 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:


Please specify the cuDNN version you want to use. [Leave empty to default to cuDNN 6.0]: 7.0.3


Please specify the location where cuDNN 7.0.3 library is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:


Please specify a list of comma-separated Cuda compute capabilities you want to build with.
You can find the compute capability of your device at: https://developer.nvidia.com/cuda-gpus.
Please note that each additional compute capability significantly increases your build time and binary size. [Default is: 7.0]7.0


Do you want to use clang as CUDA compiler? [y/N]: 
nvcc will be used as CUDA compiler.

Please specify which gcc should be used by nvcc as the host compiler. [Default is /usr/bin/gcc]:


Do you wish to build TensorFlow with MPI support? [y/N]: 
No MPI support will be enabled for TensorFlow.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native]: 


Add "--config=mkl" to your bazel command to build with MKL support.
Please note that MKL on MacOS or windows is still not supported.
If you would like to use a local MKL instead of downloading, please set the environment variable "TF_MKL_ROOT" every time before build.
Configuration finished

ビルドを行う前に、共有ライブラリパスにcudaのディレクトリを追加する。
再起動しても設定を保持してほしいので、/etc/ld.so.confに書き込む。

echo "/usr/local/cuda/lib64" >> /etc/ld.so.conf
sudo ldconfig

ビルドの実行

bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

上記実行すると、/tmp/tensorflow_pkg配下にwhlファイルができている。
これをインストールする。

sudo pip3 install /tmp/tensorflow_pkg/tensorflow-1.4.0-cp35-cp35m-linux_x86_64.whl

ソースコードビルドするのが面倒な人向けに、cuda9+cudnn7.0.3向けにビルドしたバイナリを以下においてみた。

github.com

あらためて、Tensorflowのexampleを実行してみる。

python3 $HOME/tensorflow/tensorflow/examples/tutorials/mnist/mnist_deep.py
...
2017-11-27 12:54:46.049508: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:900] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2017-11-27 12:54:46.049967: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1064] Found device 0 with properties:
name: Tesla V100-SXM2-16GB major: 7 minor: 0 memoryClockRate(GHz): 1.53
pciBusID: 0000:00:1e.0
totalMemory: 15.77GiB freeMemory: 15.36GiB
2017-11-27 12:54:46.049991: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1154] Creating TensorFlow device (/device:GPU:0) -> (device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:1e.0, compute capability: 7.0)
step 0, training accuracy 0.1
step 100, training accuracy 0.82
step 200, training accuracy 0.9
step 300, training accuracy 0.88
step 400, training accuracy 0.9
step 500, training accuracy 0.96
...

ちゃんとGPUを使って動いた。

PyConJP 2017に登壇してきた

ずっと放置していたブログを、久しぶりに書く。
PyConJP 2017に初参加・初登壇してきた。
ちなみにTalk内容はこれ。

pycon.jp

登壇資料はこれ。

speakerdeck.com

はてブ数337ブックマークついた。ホットエントリー入りした。 今回のPyConJPの資料の中では一番はてブはついたんじゃないかと思う。

ちなみに開催前に座談会のオファーを頂き、
CodeZineデビューもしました。

news.infoseek.co.jp

発表の振り返り

スライドの内容について

  • 発表内容がありきたりな技術説明が多くなってしまったと思う
    PyConという場は初参加だったが、Sparkのようなビッグデータ領域の技術に興味がある、使ったことがある人はそれほど多くないかな、と予想した。
    なので、「Sparkのいろは」的なところから説明するような発表内容にした。
    結果的に、Toggetterの反応を見る限り、狙ったとおり、Spark使ったことがないというユーザにある程度興味をもってもらうことには成功したように思う。

togetter.com

ただ、一部のSparkをガンガン使っている人にとっては、前半部分特に知ってる内容でつまらなかったんじゃないかと反省。
いろんなレベルの人に満足してもらえる発表は難しい。。

テーマについて

本当は、発表に盛り込みたい内容として、もう一つあった。
それは、「入社して2ヶ月で月間3000万UUを支える分析基盤をほとんど一人で作った話」。

今回PyConにCfPだしたのは、この件でSparkを使ったからだった。
わりと、ここのはなしもつらみとか工夫とかいろいろあって、その話もしたかったけど、
Pythonとの関連性が低かったから入れなかった。
どこかで昇華させたい。笑

登壇準備とか当日の流れとか

  • 仕事がいろいろあった+体調崩してて、発表前日の夜にあわててスライドとデモ作った。
    発表練習も1度もできないままの登壇になった。
    もう少し前からちゃんと準備して発表練習すればよかった。
  • アクシデントでパワーポイントが使えなかった。事前にspeaker deckに登壇資料あげていたので、とりあえずそれでできたけど、
    今何分経ったかもわからない状態でしゃべることになったが、その割にだいたい時間どおりに話せたのは奇跡だった。
    今度からPCは2台持ちにしよう。

登壇の感想

  • 登壇のあと、質問をしてくれる人が何人かいた。昼食の時間を削ってまで熱心に質問してくれるのはとても嬉しかった。
  • スピーカーTシャツを着ていたためか、懇親会でも話しかけてもらえた。自分から話しかけるのが苦手なので、とてもありがたかった。
    わたしはPython自体にとても詳しいわけではないので、むしろ教えていただく事が多く、本当に貴重な機会だったと思う。

全体の雰囲気とか

  • とにかく人が多かった。
  • 昼はもちろん、早めに行くと朝弁当も出る。懇親会のごはんも美味しかった。
  • やってみた系の話から機械学習などの特定技術の深い話や事例の話まで、非常に幅広かった。
    Python自体にそこまでこだわらなくてもいいっぽい?

自分が聞いたセッションで面白かったものいくつか

  • 1日目は質疑応答の対応とかいろいろで、セッションきけなかったので、2日目のみ。。

基調講演: Pandasコアコミッターが語る 今日から誰でも始められるOSS活動のススメ

OSS活動の生の話が聞けてよかった。
OSSコミュニティに限らずチーム開発を行う上でのヒントになりそうな内容がたくさんあった。

speakerdeck.com

Running Dask in the Cloud

PySparkと違ってpure Pythonで動く並列分散処理フレームワーク daskの説明。
PySparkと比べたときのメリットとして、複雑な分散コンピューテーションが可能とのこと。
どういうことかあとで調べる。

Geospatial data analysis and visualization in Python

geopandasなどを使った対話的な地理データを使った分析とビジュアライゼーション。
食べログデータを使っているとのこと。
デモが分かりやすかった。

SREエンジニアがJupyter+BigQueryでデータ分析基盤をDev&Opsする話

ちょっと疲れたのでyoutubeで聞いてた。
ここ3ヶ月の私と同じ課題と立ち向かっていそうなのでこの人と会話してみたかった。

speakerdeck.com

さいごに

こんな大きなイベントを企画遂行している運営の方はすごい。
ありがとうございました。
参加者の方々も、皆様お疲れ様でした。

EMRのスレーブノード(コアノード)にSSHアクセスする

EMRでは、セキュリティオプションでEC2キーペアを選べば、 そのキーペアを使用してマスターノードにはSSHアクセスできますが、スレーブノードにアクセスしたいときはどうすればよいのでしょうか。

SSHエージェントを使うことで、スレーブノードへのアクセスもできるようになります。 コマンドは以下の通り。

localhost$ ssh-add ~/.ssh/keypair.pem
localhost$ ssh -i ~/.ssh/keypair.pem -A hadoop@emr-master
emr-master$ hadoop dfsadmin -report |grep ^Name |cut -f2 -d: |cut -f2 -d' '
10.187.XX.XX <- slaveのIPアドレス
10.167.YY.YY
emr-master$ ssh hadoop@10.187.XX.XX