Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

CIKM2017聴講メモ(後編)

- はじめに -

シンガポールで行われているInternational Conference on Information and Knowledge Management (CIKM) 2017に参加した。

workshop day + main conference day (3day) の合計4日間。
メモはその場で書いたものを少し編集しただけで、論文を詳しく読めているものと読めていないものがある。論文読みは別途。
メモだけは長くなりそうなので前半後半に分ける。

以下が前半

各文献は以下から追える
dblp: 25. CIKM 2017


 

- Conference(3日目) -

大雨で笑った。シンガポール蒸し暑いし、スコールが厳しい。
また最後のセッションが3つしかないのはDEMOみたいなのがあったからで、今回はそちらを見ていた。

Metaricaみたいなデータ分析、ビジュアライズツールの作者がDEMOをしていた。
Metarica:https://hpi.de/naumann/projects/data-profiling-and-analytics/metacrate.html
他で気になったのは特段無かったかなあ…

 

Online learning, Stream mining

BoostVHT: Boosting Distributed Streaming Decision Trees

オンラインなBoostingは並列化難しいと言われているが、提案するBoostVHTなら分散エンジンにも乗るし分類も適切にできるよという話。

Vertical Hoeffding Tree(VHT)は、1つのModel Aggregatorが複数の統計計算をコントロールしてその結果を使って学習していくモデル。計算した複数の結果から上位を選んで(木であれば)葉の分割するしないを決定してModel Aggregatorが更新されていく。

VHTのつらいところはOzaBoostとかAdaBoost、OSBoostといった最近のBoostingモデルに適応する時に、各インスタンスの重みを変更する部分で相互接続が必要になること。なので、Model Aggregatorの部分を以下のように逐次的な感じにしてやれば良いのではという話。

f:id:vaaaaaanquish:20171118225545p:plain:w350f:id:vaaaaaanquish:20171118225553p:plain:w350<

既存のMOA(OzaBoostの並列拡張)と同等くらいに早くて精度が高い。
Boosting好きそうな登壇者とBoosting好きそうな質問者による質疑があり、Git公開も考えてるとのことだった。

VHT: Kourtellis, Nicolas, et al. "VHT: Vertical hoeffding tree." Big Data (Big Data), 2016 IEEE International Conference on. IEEE, 2016.
MOA: Bifet, Albert, et al. "Moa: Massive online analysis." Journal of Machine Learning Research 11.May (2010): 1601-1604.
SAMOA: Morales, Gianmarco De Francisci, and Albert Bifet. "SAMOA: scalable advanced massive online analysis." Journal of Machine Learning Research 16.1 (2015): 149-153.

 

Stream Aggregation Through Order Sampling

論文: Stream Aggregation Through Order Sampling

ユニークキーの無いデータで効率的にオーダーサンプリングするアルゴリズム、Priority-Based Aggregation(PBA)の提案。

スライドにも数式多く、時間ないと分からない部分が多すぎるので論文を読む。

 

FUSION: An Online Method for Multistream Classification

論文: FUSION

ストリーム状のデータ分類では、データにあるドメイン情報があるはずで、それらを利用してsourceを学習した結果を活かしながらtarget学習できるアーキテクチャを提案。

f:id:vaaaaaanquish:20171118233930p:plain:w350

図内のDensity Ratio Estimation Module(DRM)が入力データストリームと今までの分布の差を計算、Drift Detection Module(DDM)に回りしきい値を超えた場合に「入力データ分布が変わった(driftした)」として学習が進むアーキテクチャ

結果いい感じだけどどういった状態で使うかなぁというイメージ。

 

Deep Learning 1

部屋を間違えて1つ目の発表を聞き逃す等した

Length Adaptive Recurrent Model for Text Classification

論文: Length Adaptive Recurrent Model for Text Classification

テキスト分類のLSTMに対して以下のようにFCN付けて拡張したLength Adaptive Recurrent Model(LARN)を提案。

f:id:vaaaaaanquish:20171118235602p:plain:w350

まあテキスト全体と一部の効果どっちも欲しいよねという前提で分からんでもないモデル。
結果はバニラLSTMと比較しているのだが、質疑で「バニラLSTMとの比較はフェアではないのではないか」とかなり激しめの口調で指摘する人が居るなどしていた。心が痛い。

 

Multi-Task Neural Network for Non-discrete Attribute Prediction in Knowledge Graphs

論文: Multi-Task Neural Network for Non-discrete Attribute Prediction in Knowledge Graphs

Graph Embeddingの新しいネットワークの提案。
ネットワークのRelationalとAtributeそれぞれ入出力にした1つのネットワークMULTI-TASK KNOWLEDGE GRAPH NEURAL NETWORK (MT-KGNN)。

f:id:vaaaaaanquish:20171119002056p:plain:w350

気持ちは分からなくもないが、何で左右に挟まれてたりするかは聞いてたが謎だった…

 

Movie Fill in the Blank with Adaptive Temporal Attention and Description Update.

論文: http://delivery.acm.org/10.1145/3140000/3132922/p1039-chen.pdf?ip=175.177.5.173&id=3132922&acc=OPEN&key=4D4702B0C3E38B35.4D4702B0C3E38B35.4D4702B0C3E38B35.6D218144511F3437&CFID=822711636&CFTOKEN=60985772&__acm__=1511019051_cba0bc5f027c502b908eb39acc33e0d2

動画から文章が出てほしいタスクを解くため、大きめのネットワークモデルを提案。
なんかまずは穴埋めからやっていた。

f:id:vaaaaaanquish:20171119002845p:plain:w350

モデルの図はデカいけど、やってる事はシンプルで多層LSTM挟んでConcatしてMLP。結果はめちゃくちゃ良いわけではないような感じだったがこれいかに。


 

- Conference(4日目) -

Efficient Learning

Sequence Modeling with Hierarchical Deep Generative Models with Dual Memory

論文: Sequence Modeling with Hierarchical Deep Generative Models with Dual Memory

discrete sequencesで似たテキストが欲しい時、RNN、CNNだとOverfitting、Lack diversityしやすい。Deep Generative Modelsだとpoor result。
長期、短期の記憶を考慮したVAE拡張のsequenceアーキテクチャを提案。

Dual Memoriesと呼んでいて、Encoding側にBroad Memory(B-HDGM)、Generative側にDeep Memory(D-HDGM)を組み合わせ接続したBi-BD-HDGM。
f:id:vaaaaaanquish:20171119181716p:plain:w350

デカいすぎてもうこれ分からんなという感じで、学習時間もVAE等に比べて遅くなるが、結果は良い感じの文章。
質疑では、良くない文章が生成される場合もありそうだとの指摘があったが全般的に良くなる(?)といった回答だった。

 

Active Learning for Large-Scale Entity Resolution

論文: Active Learning for Large-Scale Entity Resolution

IBM Researchの人。発表が丁寧だった。
「アイツだれだっけ…」を解決したい(Entity Resolution, ER)。TwitterIBMのEnterpriseデータをマッチングしたいけどデカいし全然マッチングしないので、両者から適切に検索しマッチングさせたい。
そこでactive learningベースで高いprecisionとrecallを出せるERLernを提案。

プロファイルデータのマッチングのルールを学習(名前ANDステータスOR…みたいな)。
そのルールを元にqueryを生成してマッチングユーザを探す。

f:id:vaaaaaanquish:20171119185337p:plain:w350

Twitterとのマッチングとか、入力になる特徴量を増やすのが難しいのでこういったアーキテクチャは使われそう。

 

Indexable Bayesian Personalized Ranking for Efficient Top-k Recommendation

論文: Indexable Bayesian Personalized Ranking for Efficient Top-k Recommendation

Top-K推薦検索のタスクでは速度と精度どちらも大事。
MFはitem行列デカい。exhaustive-searchはpracitialじゃない(力任せ探索は実践向きじゃない)。

item Vectorをhash tableかけてTop-K itemをだすLocality Sensitive Hashing(LSH)、Maximum Inner Product Search(MIPS)、Nearest Neighbar Search(NNS)、Maximum Cosine Similarity Search(MCSS)あたりあるけど内積検索と同等レベルではない。

Bayesian Probabilistic Matrix Factorizationを参考に、相対的なTop-Kを出せるIndexable Bayesian Personalized Ranking(Indexable BPR)を提案。
speedも精度もかなり上がってハッピーそう。

Bayesian Probabilistic Matrix Factorization: Salakhutdinov, Ruslan, and Andriy Mnih. "Bayesian probabilistic matrix factorization using Markov chain Monte Carlo." Proceedings of the 25th international conference on Machine learning. ACM, 2008.

 

Latency Reduction via Decision Tree Based Query Construction

論文: Latency Reduction via Decision Tree Based Query Construction

Facebook社の人の発表。
Linked Inのジョブレコメンド、ジョブサーチについて。

query生成にはTerm MatchとDecision TreeとLinear Modelが入ってるらしい。
検索では当たり前になりつつあるWANDオペレータとFLEXオペレータで繋げたqueryを生成。

f:id:vaaaaaanquish:20171119192306p:plain:w600

アーキテクチャAWS配置がプレゼン内に出てきたが、さほど複雑な使い方はしておらず、拡張子しやすさやDecision Treeによる確認、改修のしやすさを大事にしている段階とのこと。
というかFacebook社もAWSだったの何気に初めて知ったかも。

 

Adversarial IR

Sybil Defense in Crowdsourcing Platforms

論文: Sybil Defense in Crowdsourcing Platforms

クラウドソーシングって色んな所で活用できるけど、悪意のあるユーザがアカウントを大量に使って攻撃してきた場合に品質保証できないから、対策したい。
従業員の類似性を定量化するフレームワークで、いくつかの質問で悪意あるユーザをグルーピングしていくだけでなく、オンラインに検出する方法を提案。

普通にいくつかの質問の答えに重みを付けていたのでう〜んという感じ

 

HoloScope: Topology-and-Spike Aware Fraud Detection

論文: HoloScope
Github: GitHub - shenghua-liu/HoloScope: HoloScope: Topology-and-Spike Aware Fraud Detection

ネットの詐欺師の検出。大体悪意あるユーザは大量の偽アカウントやIP大量購入したりしてくるので、一般的な手法だと攻撃として目立ちにくい。

なんなら中国では、国民の53.1%がネットに触れていて、平均3時間/日≒8年分が1日に消費されていて世界2位で、年間で7,500億ドルが動いている。Methbotのfakeレビューやfake投稿は日に3000億件あるし、それらが5百万ドル稼いでると予測されているという話からスタート。規模〜。

手法的には簡易で、Graphトポロジーと時間軸におけるスパイクを利用してユーザを検出。そのユーザ分けに使う動的な重み付け、ユーザ評価を含んだ検出フレームワークを提案。

 

Building a Dossier on the Cheap: Integrating Distributed Personal Data Resources Under Cost Constraints

論文: Building a Dossier on the Cheap

TwitterとかNetflixとか色んなデータを合わせれば、特定個人の個人情報や機密情報って結構作れるから危ない。公開するデータの危なさを評価するフレームワークと、予測モデルみたいなのを作ったという話。

特段難しい事はしていないが、今後こういったタスクをやる時にまた読むかもくらい。

 

DeMalC: A Feature-rich Machine Learning Framework for Malicious Call Detection

論文: DeMalC

アリババ社の人の発表。
中国では2015年に590,000件電話詐欺が発生しているし、悪意ある電話本当損失になる。
かと言って、データは無いしあっても不均衡だし詐欺師は検出されないよう進化するし、ブラックリストでもなかなか対策できない。

なのでIPアドレスから端末UIDからありとあらゆるデータをマイニングして判別するようなDeMalCフレームワーク作りましたという内容。
精度が91%になっているけど、実際それで検出するのはなかなか…という感想。

 

Representation learning

An Attention-based Collaboration Framework for Multi-View Network Representation Learning

論文: An Attention-based Collaboration Framework for Multi-View Network Representation Learning

いろいろなネットワークの形から目的とする人について学習タスクでも、DeepWak, LINE, w2vみたく、ノード空間の埋め込みがやりたい。

複数のネットワークをそれぞれ学習させるのではなく、まとめて1つの入力にする。
f:id:vaaaaaanquish:20171119204118p:plain:w350

こういった複数ネットワークを入力にするモデルはA co-regularized multi-view spectral clustering model (CMSC)、 A multi-view non-negative matrix factorization model(MultiNMF)等があるがそれらともまたちょっと違っていて、ちょっとSOMっぽい(?)。

辞書共有などは特段しているわけではないがFuture work。

CMSC: Kumar, Abhishek, Piyush Rai, and Hal Daume. "Co-regularized multi-view spectral clustering." Advances in neural information processing systems. 2011.
MultiNMF: Liu, Jialu, et al. "Multi-view clustering via joint nonnegative matrix factorization." Proceedings of the 2013 SIAM International Conference on Data Mining. Society for Industrial and Applied Mathematics, 2013.

 

Representation Learning of Large-Scale Knowledge Graphs via Entity Feature Combinations

論文: Representation Learning of Large-Scale Knowledge Graphs via Entity Feature Combinations

Knowledge Graphに対する手法でTranslation-baseなモデルではTransE, TramsH, TransR, TransDがある。

TransE: h+r=t
TransH: h-w_{r}^{T}hw_{r}+r=t-w_{r}^{T}tw_{r}
TransR: hM_{r}+r=tM_{r}
TransD: h+h_{p}^{T}hr_{p}+r=t+t_{p}^{T}tr_{p}
となってる訳だけど、このバイアスになってる  r でentityとrelation featuresを同時に表現したい。
そこにプラスとマイナスのcombinationを入れてやってScoreでマージするCombinEを提案。SOTA。

TransE: Bordes, Antoine, et al. "Translating embeddings for modeling multi-relational data." Advances in neural information processing systems. 2013.
TransH: Wang, Zhen, et al. "Knowledge Graph Embedding by Translating on Hyperplanes." AAAI. 2014.
TransR: Y. Lin, Z. Liu, M. Sun, Y. Liu, and X. Zhu. Learning entity and relation embeddings for knowledge graph completion. In Proceedings of the Twenty-Ninth AAAI Conference on Artificial Intelligence, January 25-30, 2015, Austin, Texas, USA., pages 2181–2187, 2015.
TransD: Ji, Guoliang, et al. "Knowledge Graph Embedding via Dynamic Mapping Matrix." ACL (1). 2015.

  

Learning Edge Representations via Low-Rank Asymmetric Projections

論文: Learning Edge Representations via Low-Rank Asymmetric Projections

Googleの人。
Node Embeddingを2つつくって組み合わせDeep Walkの深いverを提案。

f:id:vaaaaaanquish:20171119212309p:plain:w350

コードが公開してあるから使ってくれよなって
GitHub - google/asymproj_edge_dnn


 

- おわりに -

総評は1日目のメモに結構書いたけどDeepやオレオレフレームワークな話とGraph Embeddingが多め。

最後もうしんどくなって来てメモも適当になっていた。
4日間人の話を聞くだけというのはしんどい。

シンガポールは雨と蒸し暑さがすごい!」と聞いていたのだけど、行き帰りに雨降ったのはは1日だけだったし、しんどくなる程の暑さでもなかった。


バスやタクシー、地下鉄だけでなく、貸し自転車や貸し電動キックボードが街中にあり、サラリーマンが朝電動キックボードで通勤していたりする姿があって先進的だった。

食べ物も悪くないし、100m歩けばマクドナルドがあるし、サブウェイ、スタバ、ユニクロ、無印などがあるので不自由しなさそう。物価は1.5倍くらいだけど、中華とか安いお店が沢山ある。

まあでも2度はいいかなシンガポール

 

CIKM2017聴講メモ(前半)

- はじめに -

シンガポールで行われているInternational Conference on Information and Knowledge Management (CIKM) 2017に参加した。

workshop day + main conference day (3day) の合計4日間。
メモはその場で書いたものを少し編集しただけで、論文を詳しく読めているものと読めていないものがある。論文読みは別途。
メモだけは長くなりそうなので前半後半に分ける。

全体の所感としては、SIGIRの時と同じく「Deep Learning」「Embedding」「Social Network Analytics」「Knowledge Graph」といった単語がメインでそれらをどうやってレコメンドエンジンやランキングのようなIRに繋げようかという話が多かった。
(まあ、SIGIR協賛に居るから…)

シンガポール外はめっちゃ暑いのに、会場クーラー効きすぎて寒い。
アジアだし中国人が大体4~6割くらい。みんな寒くてコーヒーを飲みに外に出ている様子だった。
セッションが並列だったのでどれ聞いたかはタイトルと勘。

後半も書いたので追記


文献は以下から追える
dblp: 25. CIKM 2017

 

- Workshop -

2017/11/06はワークショップだった。
「登壇者来てないのでこのセッションは2人で終わりです」みたいなのが2回あって、マジかってなった。
聞き取りに必死で、割りと殴り書きメモ。
 

バイオメディカル分析

専門ではないが、DTMBioちょっと気になってたのでバイオメディカル系の話を聞いた。
DTMBio – Data and Text Mining in Biomedical Informatics

CNNでレントゲンから人の身体の異常を見つけるとか、人体の代謝(Human Metabolism)の仕組みを分析して製薬する話を聞いた。
あと、人間の構造をネットワークと捉えれば何種類かに分類できるとか。

大体内容はありがちなDeepNetやk-means等の機械学習器を使っている感じだった。
質疑では、実務系の質問以外にも「よく知られたDeepモデルとハイパパラメータとReLUを使っているが他は試したか」という内容があり、特段試してはないとの事だった。

専門用語が多くて英語だと事前知識がないと厳しかった…

 

ソーシャルメディア分析とスマートシティの実現

SMASC2017

シンガポールはスマートシティを目指してるらしく「シンガポールが国としてオープンデータを提供しているからソレとソーシャルメディアを合わせて解析したぜ!」という話がいくつかあった。

確かPyCon2017の時に聞いたのだけど、シンガポールではタクシーのGPS情報がAPI経由で取得できたりする。
たしかこれ
Taxi Availability-Data.gov.sg

あと都市のあらゆるデータが見れたり
Projects | Cities of Data

加えて、Telcoからユーザ行動ログを貰ったり、バスや監視カメラの画像まで貰って研究が進んでいるとのことだった。

質疑でも「どうやってそのデータを手に入れていくの?個人情報は?」という質問があって、「Academic Power」と応えてたのでスゲーってなった(もちろん解決しなければいけない課題があるとも言ってた)。


上記に加えてソーシャルメディアを分析に使うのはかなり有効で、結果も出るんだけど、データの全体感を掴んでhadoopとか色々適切なツールや分析手法を選ぶのが大事。
適切に時系列を選択したり、ノイズをクリーニングしたり。

街中で行われるイベントを例に出して、Twitterはイベント発生前に何度かバーストして、イベント発生直前に一気に伸びて、発生後は1~2日経ったら余計な情報しか残らないよねってグラフを出してた。


面白かった所だと、タクシー文化だけど実はUberみたいなサービスの方が使われてる(でもタクシー数はほとんど減ってない)とか。

 

Customer churn prediction(顧客解約予測)

顧客の解約予測は、時間にとてもsensitiveであるはず(退会原因のトリガーは必ず直近にある)。
だが、原因になる要素は複数あるし、その時間軸もユーザによって違うので「データの不例が定義しにくい」課題がある。

そこでSupervised learningではよくないのでPU learning使いますという話。
長期間のデータの分析結果と短期間のデータに対してPU learningモデリングした結果を見比べるアーキテクチャを提案。

PU Learningは、不例データが定義しにくい場合に使う手法で以下辺り見ると良い。
Elkan and Noto (2008): Learning Classifiers from Only Positive and Unlabeled Data | LingPipe Blog
PU Learning: Learning from Positive and Unlabeled Examples
正例とラベル無しデータからの学習 (PU classification) – nktmemo

すごくシンプルに解決できていた(ように見えた)し、一般的に解約予測で言われるような経験則よりAUC良かったので、まあそういう方向性もあるかという感じ。
質疑で「データの公開はあるか」と聞かれて「自社データなのでないです」と答えていたので本当に使えるかは謎。
ちょうどIBISでもPULearningが話題に上がったらしいが…

 

ソーシャルにおけるSinglishの分析

bigtransport17

ソーシャル分析って良いけど、多言語とかローカル性があってノイジーだから気を付けないとという話。

特にシンガポールには、英語ベースだけど中国や東南アジアの訛が混ざった「Singlish」というやつがあって、本当最悪みたいな話からスタート。
前処理における名前やURL、ストップワードの調整などを紹介。

word2vecしてLSTM、CNNに突っ込んだという話だった。


日本語Twitterも難しいから「そうだよね…つらいよね…」ってなった。

それでもw2v + LSTMである程度まで解析できるのですごい。

 

ユーザのなりすまし判定

ユーザの行動ログをDeepに突っ込んで、なりすましで金持ってく悪いユーザを検出した試みの話。
KDDやICMLでも見たGradient Boosting Decision Tree (GBDT)でfeature transformationしてConvolutional Neural Network(CNN)に入れるやつを使っていた。
GBDTを使ったfeature transformationの適用例

行動ログと端的に言っても、アクセスログや閲覧時間、視線追跡データ等があり、それら形式の違うものを組み合わせられるのがGBDTでfeature transformationする利点。

実際にあるアプリに組み込まれたみたいな話があり、悪徳ユーザを判定してloginされたら真ユーザにNotificationするだけで不正利用件数自体が減るらしい。

素晴らしいなと思ったが、この悪徳ユーザ判定でどれほど金が掛かってるんだろうと思った。
GPUパワーで殴り倒す感。

 

- Conference(2日目) -

Keynote: Machine Learning (Amazon)

Machine Learning @ Amazon

Amazon内ではレコメンド以外にも倉庫内で動くドローン、ロボットのチューニングも機械学習でやっていて、ノウハウをいくつか紹介(大体他企業と変わらないが)。シンプルなベイズモデルから製作し、チューニング。後に複雑なモデル、アーキテクチャに拡大していくとのこと。
「Washing Machine Learning」という単語を出し、機械学習器を定期的に洗浄し精度とデータの時系列性に対応しましょうねとも。

画像から商品を探すビジュアルサーチ、声で探すボイスサーチ、Botも作っている。
商品サマリの生成、商品サイズのレコメンド、離脱ユーザや新規ユーザもそれぞれモデル作ってる。
そういった研究がユーザに還元されていく予定。

Amazonユーザが返品する大半の理由が「商品のサイズ」と言っていたけど、実際は「商品に不満があったのをとりあえずサイズって答えてんじゃねーの…」とちょっと思った。

AmazonもDeep!Embedding!でどうチューニングしていくかを模索しているようだった。

ポケットに手を入れイカしたシャツの人だった

 

Multimedia

Jointly Modeling Static Visual Appearance and Temporal Pattern for Unsupervised Video Hashing

論文: Jointly Modeling Static Visual Appearance and Temporal Pattern for Unsupervised Video Hashing

動画データというのは概ねshort-termなActionの集合であるはずで、textやspeechに対してLSTMでやっているようなhash code化する工程が適応できるはずという課題感。

動画データは視覚的外観と時間的外観がありそれぞれ強みがある。
それぞれ対応するために、Temporal EncoderとAppearance Encoder&Decoderを組み合わせたアーキテクチャを提案。
f:id:vaaaaaanquish:20171112155905p:plain
半教師あり学習も適応できるため、学習データが少ない場合でも適応できそう。

ActivityNetとFCVIDの大規模動画データセットでテスト。
ビデオフレームの平均とPCAを利用する教師なしハッシュ化手法のIterative Quantization(ITQ)、ハッシュコードの平均値を使うMFH、LSTM形式でBinaryコードを吐くように教師なし学習させるBLSTMと比較。
全ての手法に対して優位な結果が得られていた。
ITQ: Gong, Yunchao, et al. "Iterative quantization: A procrustean approach to learning binary codes for large-scale image retrieval." IEEE Transactions on Pattern Analysis and Machine Intelligence 35.12 (2013): 2916-2929.
MFH: Song, Jingkuan, et al. "Multiple feature hashing for real-time large scale near-duplicate video retrieval." Proceedings of the 19th ACM international conference on Multimedia. ACM, 2011.
BLSTM: Zhang, Hanwang, et al. "Play and rewind: Optimizing binary representations of videos by self-supervised temporal hashing." Proceedings of the 2016 ACM on Multimedia Conference. ACM, 2016.

 

Construction of a National Scale ENF Map using Online Multimedia Data

論文: Construction of a National Scale ENF Map using Online Multimedia Data

電源周波数(Electrical Network Frequency: ENF)を解析する方法やハードはいくつかあるけど、YouTubeUstreamみたいなストリーミングで得られるデータから正確に解析する手法ほしい。出来たら違法なUst生放送とかやってる個人の住居特定等に繋げられるはず。といったモチベーション

信号処理をいくつかカマした後に、データを分割してオーバラップして地図上にビジュアライズしたよ(ENF Map)という発表だった。
短時間フーリエ変換(STFT)を拡張したGIFFTを使いましたというのが新規性(だと思う)
信号処理ワードが何個か分からずだった

高専の時の卒論とかで似たようなアイデアを見た気がする。
demo動画とデモサイトあるよって言ってたと思うんだけど、デモサイトはURLでググっても出てこなかった…
www.youtube.com


質疑では「IPアドレスや身体的特徴(Move band等から得られるやつ)ではダメなのか?」みたいなのがあったが、特徴量が1つ増えるし地方レベルで割りと正確に場所を特定できるから良いみたいな感じだった。

あとは、アメリカのデータで実験していて中国でもやりたいとか、BroadCastに拡張したいという話をしていた。

 

Dual Learning for Cross-domain Image Captioning

論文: Dual Learning for Cross-domain Image Captioning

画像からのCaption生成タスクでは、データ作りに時間と費用がかかるので、Cross-domainに学習できるアーキテクチャを提案してsource domain -> target domainの順に学習できるようにしたという内容。
転移学習(transfer learning)的な事をして環境に頑強なモデルを作りたいといったモチベーション。

アーキテクチャ図を以下に引用
f:id:vaaaaaanquish:20171112183911p:plain
メインとするImage Captionの生成はDeepなLSTM、それと並列に画像合成(Image synthesis)タスクを行うGANを走らせている。
pre-trainingさせておくことで、画像データの分布を学習した状態のGANの結果を使いながら文章生成に移れる。また、どちらも半強師あり、教師なしを交互に組み合わせて使う事ができるので、targetにする学習データは少なくて良いよという内容。

キャプション生成はCNNとLSTMを別に学習して織り交ぜるDeep Compositional Captioner (DCC)、CNN-LSTMモデルの拡張であるShow, attend and tell model (SAtT)、Show, Adapt and Tell (SAdT)と比較。pre-trainingはMS COCOでAdam等である程度最適化していた。FlickrとOxfordのデータセットをtargetにして学習。

DCC: Anne Hendricks, Lisa, et al. "Deep compositional captioning: Describing novel object categories without paired training data." Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2016.
SAtT: Xu, Kelvin, et al. "Show, attend and tell: Neural image caption generation with visual attention." International Conference on Machine Learning. 2015.
SAdT: Chen, Tseng-Hung, et al. "Show, Adapt and Tell: Adversarial Training of Cross-domain Image Captioner." arXiv preprint arXiv:1705.00930 (2017).

結果も良く、出てくる文章がかなり良さそう。
f:id:vaaaaaanquish:20171112184910p:plain

ただそれにしては複雑すぎるアーキテクチャのような気がしないでもないが…
「GANが不安定」というイメージが強いのでその辺どうなんだろうと思った。

A New Approach to Compute CNNs for Extremely Large Images

論文: A New Approach to Compute CNNs for Extremely Large Images

Art Transferの新しいモデルの提案。
画風変換だが、デカい画像を学習させようとすると複数GPUを使うことになるが、モデル複数並列にしてもモデル並列にしても結論ちょっとつらい。なので、bulk synchronization parallel(BSP)なる機構で高速な学習を実現するアーキテクチャを提案。

f:id:vaaaaaanquish:20171112194713p:plain:w300f:id:vaaaaaanquish:20171112194717p:plain:w300

単純にやるとCoordinatorなるメインのworkerに対して各GPUに遅延が発生するのだが、同じCNNカーネルを利用しても良いんじゃないかという提案だと思われる。

発表の多くが結果の説明でアーキテクチャの部分がイマイチ把握しきれていないが、生成する画像も良く画像サイズを大きくしていった時にも早い速度で学習することが可能になっている。

f:id:vaaaaaanquish:20171112195452p:plain
f:id:vaaaaaanquish:20171112195107p:plain

あまり詳しくないが、ちゃんとやらないとマズいなあと思っているジャンルのひとつ。

 

Network Embedding

このセッションに関して調べてるついでに以下見つけたのでハッピー

論文: From Properties to Links

ネットワークはubiquitonsだし応用先も多い。クラスタリングとかリコメンドとか。
UserProfileみたいなmetadataの変化とネットワークの変化をMultiViewに埋め込みたい。
加えて静的なGraph Embeddingする手法だとDeepWalk、LINE、GraRep、Node2Vec、SDNE、TADW辺りがあるが、やっぱりグラフというのはDynamicなもので、新しいNodeがきたり情報が変化した時に動的に学習できれば最高だよね。というのがモチベーション。

Graphの変化があった場合に再学習できるようCorrelation-learningを取り入れたGraph Embedding手法であるMultiView Correlation-learning based Deep Network Embedding method(MVC-DNE)を提案。

ベースはAuto Encoder状のネットワークで、添字となっているPTはマルチビューとしている2つの空間である(例えばTがネットワーク構造の情報で、PがNodeに含まれるmetadataとか)。

f:id:vaaaaaanquish:20171112211216p:plain:w300f:id:vaaaaaanquish:20171112211220p:plain:w300

Naive combinationとの組み合わせが効いてた。
新しいNodeが挿入された場合の追加での学習においてもCiteseerGoogle+、DBLPデータセット全てにおいて、AUC、classification等良くなっていた。

DeepWark: Perozzi, Bryan, Rami Al-Rfou, and Steven Skiena. "Deepwalk: Online learning of social representations." Proceedings of the 20th ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2014.
LINE: Tang, Jian, et al. "Line: Large-scale information network embedding." Proceedings of the 24th International Conference on World Wide Web. International World Wide Web Conferences Steering Committee, 2015.
GraRep: Cao, Shaosheng, Wei Lu, and Qiongkai Xu. "Grarep: Learning graph representations with global structural information." Proceedings of the 24th ACM International on Conference on Information and Knowledge Management. ACM, 2015.
Node2Vec: Grover, Aditya, and Jure Leskovec. "node2vec: Scalable feature learning for networks." Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2016.
SDNE: Wang, Daixin, Peng Cui, and Wenwu Zhu. "Structural deep network embedding." Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining. ACM, 2016.
TADW: Cheng Yang, Zhiyuan Liu, Deli Zhao, Maosong Sun, and Edward Y Chang. 2015.
Network Representation Learning with Rich Text Information.. In Proceedings of
the 24th International Joint Conference on Artificial Intelligence. 2111–2117.

 

Learning Community Embedding with Community Detection and Node Embedding on Graphs

論文: Learning Community Embedding with Community Detection and Node Embedding on Graphs

コミュニティの検出したいという内容。
既存では、DeepWalk、LINE、GraRep、Node2Vecなど埋め込みでやる代表的な手法各位以外に、コミュニティ検出向けモデルのSpectral、DNR、M-NMF、などがある。

良いところどりしたComEを提案。
Community DetectionとCommunity EmbeddingとNode Embeddingの手法をそれぞれ使いながら、その情報によってコミュニティを出していく。グラフが G=(V,E) で表されるとき、 O(|V| + |E|)でコミュニティをスケーラブルに推論できるアルゴリズムを提案。
加えてロス関数やパラメータを色々試して競ってみてる。

Githubでコードも公開しているので良さ。


Spectral: Tang, Lei, and Huan Liu. "Leveraging social media networks for classification." Data Mining and Knowledge Discovery 23.3 (2011): 447-478.
DNR: Yang, Liang, et al. "Modularity Based Community Detection with Deep Learning." IJCAI. 2016.
M-HMF: Wang, Xiao, et al. "Community Preserving Network Embedding." AAAI. 2017.

 

Attributed Network Embedding for Learning in a Dynamic Environment

論文: Attributed Network Embedding for Learning in a Dynamic Environment

やりたいタスクは2つ上のと同じで、ネットワーク+ユーザデータとなっているデータを分析するためにEmbeddingしたいという話で、ネットワークは新しいNodeやEdge、ユーザデータはPostやEventで更新されるから動的にやりたいよねというモチベーション。

Dynamic atributed network embedding(DANE)を提案し、静的な状態で行列によりそれぞれのデータを埋め込む方法と、埋め込み時の行列の変化を勾配にして学習していくオンライン埋め込み法を提案している。

いまいちアルゴリズム追い切れてないので、ちゃんと論文を読まないと把握無理そう。

 

Learning Node Embeddings in Interaction Graphs

論文: Learning Node Embeddings in Interaction Graphs

根本解きたいタスクは上記と同じ。ただネットワークにおけるNodeには、ユーザのデータとトランザクションデータのように、エンティティと時間的なEdgeが複合している場合がある(時系列データと時系列情報が同じネットワーク内にあるような場合がある)ので、そういったタスクでも解けるようにInteraction Graph Embedding(IGE)を提案。

f:id:vaaaaaanquish:20171112220123p:plain:w300f:id:vaaaaaanquish:20171112220420p:plain:w300

IGEは複雑なモデルではなく、複数の種類のデータを合わせて入力できるような構造にしたよという話なので、比較先もPV-DM、node2vec、APEとなっていて、 DBLP、PPD、Stock、Yelp各データセットでclusteringして良さそうな結果。
かなり特定の状態の時に使えるモデルかなというのが所感。

Doc2Vecとかはじめて見たの2年前なのに、Embedding相当進歩してんなあと思った。

PV-DM: Le, Quoc, and Tomas Mikolov. "Distributed representations of sentences and documents." Proceedings of the 31st International Conference on Machine Learning (ICML-14). 2014.
APE: Chen, Ting, et al. "Entity embedding-based anomaly detection for heterogeneous categorical events." arXiv preprint arXiv:1608.07502 (2016).

 

Temporal data

Covering the Optimal Time Window Over Temporal Data

論文: Covering the Optimal Time Window Over Temporal Data

いまいち掴みきれてないが、「時間軸で数を最大化するある区切られた時間窓を決定する問題」をthe optimal time window covering algorithm(OTWC)と呼び、OTWCへの2つの対応策を提案。

これは例えば、A、B、Cさんが一番長くミーティングに参加できる時間は何時から何時まで?みたいなタスク。

sliding time window algorithm(STW)とalgorithm based on Timeline Index(TLI)の2つで時系列での複雑な方が後者。
タスクを命名していたが普通に探索問題な気がした。

 

Scaling Probabilistic Temporal Query Evaluation

論文: Scaling Probabilistic Temporal Query Evaluation

時系列knowledge graphsに対するquery評価という課題に対してPRBAbilistic Temporal Query Evaluation(PRATiQUE)フレームワークを提案。

KnowledgeGraphに疎く、Google’s Knowledge Vault、NELL、YAGO、DeepDive、ReVerbといったKGsと呼ばれるKnowledge Graphの形式がある事すら知らなかったのでなかなか苦しかった。

Knowledge Graphもう少し勉強しないと...

 

Efficient Discovery of Abnormal Event Sequences in Enterprise Security Systems

論文: Efficient Discovery of Abnormal Event Sequences in Enterprise Security Systems

NEC labで研究されている侵入検知システムの話。Intrusion detection system (IDS)。

「異常検知」で言うところの「異常」はLowレベルな異常行動の塊である。たとえば、変なディレクトリ参照して -> 権限見て -> 書き換えて…のような一連の行動。

コマンドログをグラフベースにし、異常な行動をしている人が居たら検知するというフレームワークの提案。
f:id:vaaaaaanquish:20171118182902p:plain

中身は単純なTop Kのように見えたけど、毎分約200万レコード処理できて正確に侵入検知ができるようになっているとのことだった。

  

Temporal Analog Retrieval using Transformation over Dual Hierarchical Structures

論文: Temporal Analog Retrieval using Transformation over Dual Hierarchical Structures

古いオープンデータとかアクセスできても、古い用語や言葉遣いに困るので、時系列で類義語見つけて新しい言葉でも表現できると良いよねという話。

時系列順に階層状にクラスタを構造して、そこから追っていくアーキテクチャを提案。
すごい精度が上がっていたがデータセットがいまいちわからなかった。

確かにウォークマンとかiPod的なやつって言わないと分からんよなあ…ってなった。

 

おわりに

Temporal dataの話聞いたのだけど、正直ぐぬぬって感じだった(理解と英語で処理能力足りなかった)。
また勉強しなきゃという感じ。

スタートではDeep Learningの勢いがすごいですねえ!みたいな話とワードクラウドを見た。


2日目はReceptionとしてご飯食べながらポスター発表もあった。
そこもやはりDeep Learningで色んなデータをConcatして優勝、動的なKnowledge Graph、RankNetみたいな話が多かった。流行り。
本当に刺激的な1日だった。


1日目終わってから行ったマーライオンさん
https://pbs.twimg.com/media/DN8LOiNUEAApgIg.jpg
俺の会社での態度くらいデカい


 

「仕事ではじめる機械学習」を読んだので作者に媚を売る

- はじめに -

以下を読んで、筆者ら (@chezou, @tokoroten, @hagino3000) ともTwitterで相互フォローだし、いっちょ媚び売るために感想記事でも書いとくかみたいな記事。

www.oreilly.co.jp

私は「企業で機械学習プロジェクトをいくつか経験している」「書に載っているアルゴリズムや検定も大体わかる」くらいで本書のターゲットからは少し外れているっぽいのだけれど、知ったことではない。


 

- この本どんな人がターゲット? -

「仕事ではじめる機械学習」というタイトルの通り、「俺は来年から新卒社会人!大学で学んだ知識を活かして機械学習エンジニアとして頑張っていくぞ!」みたいな人が読むとすごく為になる本。
あと、ターゲットとしては「バイトで機械学習経験したい学生」とか「突然上司に機械学習やってくれって言われた!」みたいな人とか。

あと、機械学習を使った時に、プロジェクトの回し方が他の開発とは少し変わってくること、インフラ要求、事業への応え方も結構独特(正確に言うとあまり知られていない)という内容が書かれており、「弊社にもR&Dみたいな機械学習開発やってるあるんだけど、アイツらマジ何考えながらやってんの?」という人もターゲットに入りそう。

機械学習や統計を使って仕事している人の理想が描かれているので、読むと機械学習を仕事でやっている人が実際どうプロジェクトを進めているか、彼らの課題は何かが広く見えてくると思います。


 

- 感想とか -

詳細な内容は@razokuloverが丁寧にまとめてくれてるのでそれで十分だと思います。
razokulover.hateblo.jp

以下はもう自分が気になったこと、考えたことをつらつら書いていくだけです。

 

機械学習を使わない方法を考える」

この書籍では「機械学習を使おうとする前に機械学習を使わない方法を考える」という内容が序盤に数回出てきます。

これは、機械学習や統計を業務で使う上で非常に重要で、エンジニアリング面だけでなくインフラ、アプリ、経営…会社の全ての面に関わってくる非常に大事な文言です。
筆者もTwitterでドヤ顔でネタにしているくらい大事な事です。

これは最終的に機械学習やるやらないに関わらず本当に広まって欲しい内容です。
技術の不安定さや実運用の難しさを丁寧に説いており、現場の機械学習野郎が泣き喚いてるみたいな上司の方は是非本書を熟読して下さい。

 
さて少し話変わって実際問題ですが、無理に機械学習を使うプロジェクトは世の中から減るばかりか増える一方です。実際現場には「それ最頻値、中央値で良くね?」といった状況から、下手すれば「それデータ集めて整理すればif文何個か作ればよくね?」という状況まであります。

書籍に書かれている通り、「機械学習はコストになりやすい技術」です。
それでも使いますか?という疑問符を常に持っておかなければいけません。


私は実際にこういった機械学習コスト度外視プロジェクトが進む主な原因は、現場で機械学習を使わない方法を考えてない」のではなく「機械学習を使わない方法がイマイチわからない」という場合が多いのだろうと考えています ("何でも良いから人工知能使って"と言われたみたいな状況は論外にします)。


例えば、想定として画像認識のタスクをやりたいケースを考えます。
Google先生で「画像認識」で検索するとまずDeep Learningの記事が出てきます。

ここから深みにハマっていくと、やれ「Deepじゃないと出来ないんだ」「複雑な機械学習じゃないと精度の高い物は出来ないんだ」となっていくのではと考えています。実際には解きたい課題はもっと簡単なのに。

まあSNSでもDeepDeep機械学習Deepした案件が拡散されやすいですし、IT企業に属していれば誰しも脳裏に焼き付いてる何かがあると思いますし、心理の働きも多く影響してきますが、一度自分の課題を見返すことが大事です。


機械学習エンジニアの仕事には、こういった場合に「機械学習を使わない方法を提示する事」が入ってくると私は感じています。

例えば「まず色情報だけでヒストグラムを取ると簡易な手法でこれだけ精度が出ます」「既存のパッケージやシステムに割り当てるだけで十分です」「画像をクラウドソーシングに投げてまず運用しましょう」「高度な機械学習は次のフェーズにしましょう」「DeepはAWS上に構築した場合GPU費用もかさむので、独自開発せずGoogleが出してる既存APIを使いましょう」といった説得がここにあたります。

この書籍内では後半に「分析結果を上司に説明する方法」のような項があり、そちらに通ずる部分もありあますが、機械学習エンジニアにとって「機械学習を説明できる事」と同等なレベルで「機械学習を使わない方法を提示できる事」が大事な力であると、この書籍で再認識しました。

上記提案できるためには、インフラの負荷計算やコスパの算出、様々なビジネスの知見、観点、データの可視化などが必要になるため、それらを学ぶ機会があれば大事にしたい所です。


これらは、ちょっとフィルターを通して考えれば、機械学習エンジニア以外でも自分の専門のリスキーさを理解し説得できるという事が働く上で大事な技術という事ではあるのですが、"機械学習エンジニアは特に"という点で書籍内で繰り返し言われている事に良さを感じました。

 

インフラ環境

この書籍では主に機械学習を取り巻くインフラ環境として「マイクロサービス」を取り扱っています。
AWSGoogle Cloud Platform(GCP)を利用し、クラウド上のインスタンスやLambdaのようなコンピューティングサービス、RDBSやRedshiftなどのデータベース等を適切に使い分け、細かく機能ごとにサービス配置を分けるアレです。(最近ではよくピタゴラスイッチと呼ばれているソレです)。

経験上、マイクロサービスは機械学習プロジェクトにかなり適した形だと私も感じています

マイクロサービスは「プロジェクト進行具合やスケーリング等までサービスごとに分けて考えられる」というのが大きなメリットですが、最も機械学習に効いてくるのが「最悪切り離してポイできる」だと思います。

「データはナマモノ」と界隈ではよく言われますが、「時期が変われば今まで使っていた分類器がゴミになる」とか「前処理がちょっと変わって全部パー」みたいな事が多々起こり得るのが機械学習プロジェクトです。
マイクロサービスはその特性によくマッチしています。

機械学習におけるインフラ設計の考え方だけでなく、後半実例も含めながら丁寧に書かれているので、機械学習屋だけでなく機械学習プロジェクト周辺に属するインフラ屋さんにもオススメできる本だと思います。

 
強いて言えば、Workflow辺りに少し触れて欲しかったと思います。

マイクロサービスは良いところも多いですが、既存がもう1つのプロジェクトとして完成している場合が多々あります。
機械学習をその中に持ち込む事で、ピタゴラスイッチが総崩れになり復旧が大変だったという話も聞きます。

またこれらは逆も然りで、切り分けすぎて「アプリ側、バックエンド側の仕様が見えてない」がために「機械学習器の精度が上がらない」といった状況もままあります。


この辺TPOである場合が多いんですが、そのために全体を見通してサービスを管理できるフレームワークが最近徐々に使われるようになってきています。

書籍にある通りログの取得も大事ですが、ワークフローフレームワークのようなデータの取得から前処理、機械学習器を含めたフローの構築、検定以外でKPIに則しているかのテストを、1つのフレームの中で定期的に回せる事がじわじわ効いてきたりします。
もちろん両者良し悪しあるので、この書籍を読み終わった後にWorkflowについて調べてみるのも良いかもしれません。

 
つらつら書いてますが、個人的にワークフローフレームワークは嫌いで機械学習プロジェクトでは使いたくないとまで思っているんですが、まあ便利なので書籍内で取り上げてもらって筆者のプロの方々がどう考えているのか知れれば良かったなあ〜みたいな感じなので、主な理由がゲスです。

 

インフラは機械学習の本質か

インフラやアプリ側の知識、ログも取らないと…という書籍の内容には同意が9割、モヤモヤ1割という感じです。
この疑問はこの書籍に限ったことではなく、常に自分の中にあるものです。

機械学習屋の技術力は、それぞれのアルゴリズムの特性を理解しデータから統計的で適切な処理を施せるところにあると思います。
果たして「AWSの各サービスやスケーリングシステムの把握に毎日数時間使う」「アプリ側のswiftのコードを読むために数日勉強する」といった時間をどの程度取るべきなのでしょうか。それらの設定までやり切るべきでしょうか。

AWSの新しいサービスを触る時間でKaggleをやったほうが良いのでは?

 
もちろんですが、別レイヤを把握することの効能も記事の上でゴリ推ししたので、これらはTPOであり当人が選択すべき点であるという事は言わずもがなだと思います。
会社の状況や自身のキャリアプランに合わせて、その辺りも選んでいかないといけないなと思う次第です。


 

美しい強調フィルタリングからFactorization Machineの流れ

強調フィルタリングやFactorization Machineは、今やレコメンドエンジンを作る上で欠かせない技術です。
Factorization Machineはニューラルネット拡張やら行列の高速計算など発展著しく、Deep、Embedding、Rank学習、生成モデル、バンディット辺りくらいホットです。

書籍『仕事ではじめる機械学習』より、「強調フィルタリングの良し悪し」の説明から美しく「Factorization Machineを使おう!」という流れに持っていけている日本語の書籍は、他にないと思います。

筆者FM好き過ぎでは?

 
レコメンドエンジンを作りたいんじゃいという人は読むと良いです。

 

Excel

途中Excelを使うコーナーがあります。
ところてん氏の書くコーナーですが、機械学習を使わないために様々な分析をExcelで行っています。

実際見てて思ったんですが、Excelはとても高機能でポチポチでビジュアライズできる良いソフトウェアだと思います。

ただ、別にPythonやRに親和性があるわけでもなく、文字コードVBAというキワモノと一緒に生きていくことになるため、やはりMicrosoftという感じ。

 
みんなExcelに変わるやつを心の中で求めているのでは…とただただ思いました。

 

実務案件対応

最後の章では、実際に実務やデータセットを例に、どうやって機械学習の手法を選んだり、プロジェクトを進めていけばよいかが経験を元に書かれています。

私の求めていた部分は結構ここにあって、実務で使った機械学習案件って結構外に出にくいのでこういった所で読めるのはとてもありがたいです。

 
基本的に機械学習における「ドメイン情報」と呼ばれる「データに関する知見」というのは、それだけで会社の機密データや顧客の個人情報につながる場合が多く、そういった背景を理解した機械学習エンジニアの集まる勉強会でさえ詳細をボカされる場合が多いです。

精度でさえ詳しい検定結果を示さず「目視したら大体ほとんど正解でしたね〜…」等と適当にボカして言う場合が多いです。
それらをデカい声で公言した事で「あの会社の機械学習器は90%しか精度がない!1割間違えられるサービスに信用はおけない!」みたいな事を言われかねないからです (もちろんその1割をカバーする施策を裏で多くしている筈ですが)。
 
 
直接的な原因がソレという訳ではないですがGoogleが女性とゴリラを誤判定したニュース辺りから、かなりシビアになってきていると肌でも感じます。
書籍内にも「精度が100%の機械学習器は作れない」という話がありますが、本当にその通りで機械学習プロジェクトというのは機械学習単体では成り立たないのです。ただ、周辺のそれらを含めて1つ話をするというのは、とても骨が折れる事だと思います。プロジェクトの全体像を1から話す事になりかねませんからね。


そういった背景からか、結構実務の詳細な話があるというのはとてもレアなのです。
このような実務寄りの内容が外に出てくる事案が1つ増えたという事でとても価値があると思います。


 

- おわりに -

この記事には書いてませんが、実際は機械学習の主な手法をいくつか解説、実装パッケージの紹介、検定の手法の紹介など、基本的な内容もしっかり丁寧に書かれています。
Pythonの知見も溜まります。

また、コラムのようにちょいちょい出てくる機械学習界隈では当たり前になっているネタも拾えるので良さあります。

実際に私自身は序盤に書いたターゲット層から外れていると思うので「凄い本が来た!」とまではいきませんでしたが、「自分もしかしたらターゲットにフィットするかもな」と思えれば、値段もお手頃ですのでPDFで持っておく価値は十二分にある良い書籍だと思いました。


み〜んなMediumじゃん。はてなの時代は終わったのか?


 
追記:


めっちゃTogetterにまとめられてるオジサンだと思ってました。すまんせん…

 
追記2:
Workflowについて言及してくれてた


個人的にワークフローシステムに導入によって機械学習エンジニアの仕事量が明らかに増えると思っているのであまり好きになれない部分があったが、これもまたTPOなのだなと思う。
適切に見極められるようになりたい。


 

Pythonとカーネル密度推定(KDE)について調べたまとめ

- はじめに -

端的にやりたい事を画像で説明すると以下
f:id:vaaaaaanquish:20171029110340p:plain

データ標本から確率密度関数を推定する。
一般的な方法としては、正規分布やガンマ分布などを使ったパラメトリックモデルを想定した手法と、後述するカーネル密度推定(Kernel density estimation: KDE)を代表としたノンパラメトリックな推定手法がある。

本記事ではKDEの理論に加え、Pythonで扱えるKDEのパッケージの調査、二次元データにおける可視化に着目した結果をまとめておく。

 

- カーネル密度推定(KDE)とは -

x1, x2, ..., xn を未知の確率密度関数 f を持つ独立同分布からの標本としたとき、任意のカーネル関数 K、パラメータ hカーネル密度推定量は以下の式で表される。

   \hat{f}(x) = \frac{1}{nh} \sum_{i=1}^n k\bigg(\frac{x-X_i}{h}\bigg)
 
つまるところ、標本を使ったKernelを足し合わせて母集団の分布に寄せたいという話。

カーネル関数 K は Gaussian Kernelが代表的。
Rectangular、Triangular、Epanechnikov等、以下性質を満たすものが使われる。

    \int K(x)dx=1,  \int xK(x)dx=0,  \int x^{2}K(x)dx>0

それぞれの特性については後述する

パラメータ h はBandwidth、バンド幅等と呼ばれる平滑化のためのパラメータで、直感的に標本の幅やデータ数に合わせて調整する必要がある事がわかる。
経験的に調整する方法やcovariance(共分散推定)による調整方法等があり、後述する通りそれらが実装されているPythonパッケージもある。

式を見てわかるように、データ上でカーネル関数を足し合わせているだけなので計算が簡単で、分布に対する仮定も非常に少ない。
また、多変量、多次元への拡張やクロスバリデーションによる最適化も可能である。


Kernelの足し合わせでは、離散コサイン変換(DCT)、高速フーリエ変換(FFT)を利用した高速な計算方法が提案されており、一般的なパッケージではこれらが利用されている。
FFT-Based Fast Bandwidth Selector for Multivariate Kernel Density Estimation - arXiv [A Gramack, stat.CO, 2015]

FFTベースの手法以外では、N個の点のM個の評価(各入力/出力対間の距離計算)であると捉え、一般化N体問題としてkd木(kd tree)やball treeに落とし込んで解く手法がある。
sklearn等ではK近傍法 (K-nearest neighbor) の実装で使われているアルゴリズム
Alexander G. Gray's N-Body Page
survey/kdtree.md at master · komi2/survey · GitHub

kd木のようなデータ構造を使うことで、データポイントを空間的に分離して計算し高速化出来る。
木にする事で各Kernelの相対的な誤差(relative tolerance: rtol)、絶対的な誤差(absolute tolerance: atol)
をパラメータとして設定する必要があるが、パラメータに応じた以下の精度で高速に近似カーネル密度推定値を計算することが可能である事が示されている。
   abs (p-p_{t} ) < atol+p_{t} \cdot rtol
Density Estimation Trees - CMU Statistics - Carnegie Mellon University
Forest Density Estimation - arXiv [H Liu, stat.ML, 2010]


KDEの欠点として、パラメトリックな手法に比べて次元が大きい場合に収束レートが非常に遅い、データ点が多い場合のメモリ量や計算量が大きい等があり、適切なパラメトリックモデルが得られてない場合に利用する手法である。

Wikiが割りと丁寧
カーネル密度推定 - Wikipedia
ネットで先生方の講義資料が沢山あるのでGoogleでも
データ解析 第十回 ノンパラメトリック密度推定法 鈴木大慈
カーネル密度推定法(第12章) - TOKYO TECH OCW 杉山 将
カーネル法入門 カーネル法によるノンパラメトリックなベイズ推論 福水健次 統計数理研究所/総合研究大学院大学
英語だと以下辺り参考
http://www.shogun-toolbox.org/static/notebook/current/KernelDensity.html
Kernel Density Estimation in Python | Pythonic Perambulations
GetDist: Kernel Density Estimation
[1704.03924] A Tutorial on Kernel Density Estimation and Recent Advances


 

- Python KDEパッケージの比較 -

調べて出てきたパッケージとKDEの実装クラスを以下に挙げる

  • SciPy
  • sklearn
    • class: neighbors.KernelDensity
    • doc
    • 独自実装 [ Github ]
    • Tree algorithmで2種類 ["kd_tree"|"ball_tree"]
    • 木なので距離関数metric、葉数leaf_size、前述のatolとrtolを調整する
    • Metric: 11種類 [“euclidean", “manhattan", “chebyshev", “minkowski", “wminkowski", “seuclidean", “mahalanobis", “haversine", “hamming", “canberra", “braycurtis"] + 自前実装 [ doc ]
    • Kernel: 6種類 ["gaussian", "tophat", "epanechnikov", "exponential", "linear", "cosine"]
    • Bandwidth: 自前で用意する必要があるがGridSearchができる
  • Statsmodels
    • class: api.KDEUnivariate, api.KDEMultivariate
    • doc, tutorial
    • 独自実装 [ Github ]
    • KDEUnivariateが一次元でFFTベースの高速実装
    • KDEMultivariateが多次元でFFT効率悪いので分けられているがこちらはcross-validationが使える
    • Bandwidth: 3種類 ["scott", "silverman", "normal_reference"]
    • Kernel: 7種類 ["biweight", "cosine", "Epanechnikov", "Gaussian.", "triangular", "triweight", "uniform"] + 自前実装
  • PyQt-Fit
    • class: kde
    • doc, tutorial
    • Bandwidth or Covariance: 4種類 ["variance_bandwidth", "silverman_covariance", "scotts_covariance", "botev_bandwidth"] + 自前実装
    • Kernel: 4種類["Gaussian", "Tricube", "Epanechnikov", "Higher Order"] + 自前実装
    • Kernelの中のパラメータやデータポイントごとのbandwidth設定(lambdas)、データポイントごとの重み(weights)、methodではカーネルの足し合わせ方まで選べてわりと高級
    • PyQtなるGUI実装のためのパッケージだが切り離されている
    • 使えるところでDCTかFFTを使ってくれる
  • AstroML
    • class: density_estimation.KDE
    • doc
    • 一応書いたが「sklearn優秀なのでこっちは0.3で廃止するね」と言ってる

scipy, statsmodels, sklearn, pyqt-fitの4つが大体メイン。
実装されているアルゴリズムや方針はそれぞれ。
scipy以外は大体どれも自作のbwやKernelが使える。
入力で言うとsklearn以外はlistそのまま突っ込める。sklearnだけnp.array[:, None]。

 

以下利用するデータセット

後述のスクリプトではscikit-learnで取得できるirisのデータセットを利用する。

pip install numpy
pip install pandas
pip install scikit-learn
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from sklearn import datasets

# irisデータセットサンプル
iris = datasets.load_iris()
# pandas
data_pd = pd.DataFrame(data=iris["data"], columns=iris["feature_names"])
display(data_pd.head())
# 以降ではirisの"petal length (cm)"カラムを利用する
# list
data_list = [x[3] for x in iris["data"]]
# np.array
data_np = np.array(data_list)

# x軸をGridするためのデータも生成
x_grid = np.linspace(0, max(data_list), num=100)
# データを正規化したヒストグラムを表示する用
weights = np.ones_like(data_list)/float(len(data_list))

f:id:vaaaaaanquish:20171029144025p:plain:w400:h180

 
グラフ描画は基本的にmatplotlib

import matplotlib
import matplotlib.pyplot as plt
plt.style.use('ggplot')

  

pandas

多分一番使われているだろう最も簡単なやつ
中身はscipyでgaussian_kdeが動いている

plt.figure(figsize=(14,7))
data_pd["petal length (cm)"].plot(kind="hist", bins=30, alpha=0.5)
data_pd["petal length (cm)"].plot(kind="kde", secondary_y=True)
plt.show()

Matplotlib内のKDE, secondary_y=TrueでY軸正規化して重ねられるので便利
f:id:vaaaaaanquish:20171029144456p:plain:w400:h200

 

scipy

1.0.0のリリースおめでとうございます。
gaussian_kdeのみだが、default値でかなりよしなにやってくれる。

pip install scipy

最もシンプルに書くと以下

from scipy.stats import gaussian_kde

kde_model = gaussian_kde(data_list)
y = kde_model(x_grid)

plt.figure(figsize=(14,7))
plt.plot(x_grid, y)
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.show()

f:id:vaaaaaanquish:20171029170718p:plain:w400
 
Bandwidthの推定はdefaultで以下scotts_factor
n**(-1./(d+4))
以下silverman_factorも選べるので比較してみる
(n * (d + 2) / 4.)**(-1. / (d + 4)).

scotts = gaussian_kde(data_list)
y_scotts = scotts(x_grid)
silverman = gaussian_kde(data_list, bw_method='silverman')
y_silverman = silverman(x_grid)

plt.figure(figsize=(14,7))
plt.plot(x_grid, y, label="scotts")
plt.plot(x_grid, y_silverman, label="silverman")
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029170744p:plain:w400
 
自前の関数でもできる

def kde_bw(obj):
    return obj.n * 0.001

kde_model_origin = gaussian_kde(data_list, bw_method=kde_bw)

f:id:vaaaaaanquish:20171029170801p:plain:w400

 

scikit-learn

他と違ってtree-baseな構造を使っていてかなり高速。
sklearnに親しみがある人が多いと思うし、記述も簡易。

 

基本的なKDE
# -*- coding: utf-8 -*-
from sklearn.neighbors import KernelDensity

# default
kde_model = KernelDensity(kernel='gaussian').fit(data_np[:, None])
score = kde_model.score_samples(x_grid[:, None])
# bw値を調整
bw = 0.1
kde_mode_bw = KernelDensity(bandwidth=bw, kernel='gaussian').fit(data_np[:, None])
score_bw = kde_mode_bw.score_samples(x_grid[:, None])

# マイナスが出るので指数関数かます
plt.figure(figsize=(14,7))
plt.plot(x_grid, np.exp(score), label="default")
plt.plot(x_grid, np.exp(score_bw), label="origin")
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029154709p:plain:w400
出力からわかるようにBW値の設定は必須である。

 

Using Kernel

sklearnで使えるKernelを全て見てみる。
それぞれのKernelの数式は以下にまとまっている。
2.8. Density Estimation — scikit-learn 0.19.1 documentation

# Kearnels
plt.figure(figsize=(14, 7))
plt.grid(color='white', linestyle='-', linewidth=2)
X_src = np.zeros((1, 1))
x_grid = np.linspace(-3, 3, 1000)
for kernel in ['gaussian', 'tophat', 'epanechnikov',
               'exponential', 'linear', 'cosine']:
    log_dens = KernelDensity(kernel=kernel).fit(X_src).score_samples(x_grid[:, None])
    plt.plot(x_grid, np.exp(log_dens), label=kernel)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029154411p:plain:w400

理論通りの結果が見られる。
これらを使いKDE値を計算してplotしてみると以下。

# plot
plt.figure(figsize=(14,7))
for kernel in ['gaussian', 'tophat', 'epanechnikov',
               'exponential', 'linear', 'cosine']:
    kde_modes = KernelDensity(bandwidth=bw, kernel=kernel).fit(data_np[:, None])
    scores = kde_modes.score_samples(x_grid[:, None])
    plt.plot(x_grid, np.exp(scores), label=kernel)
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029154419p:plain:w400

それぞれのKernelの形を足し合わせて元の分布を表現していることがよくわかる。

 

GridSearch

上記のようなパラメータをGridSearchでクロスバリデーションできる。
ここはsklearnらしさある。

from sklearn.model_selection import StratifiedKFold

grid = GridSearchCV(KernelDensity(),
                    {'bandwidth': np.linspace(0.1, 1.0, 10)},
                    cv=20)
grid.fit(data_np[:, None])
print(grid.best_params_)

kde_model_best = grid.best_estimator_
best_score = kde_model_best.score_samples(x_grid[:, None])

plt.figure(figsize=(14,7))
plt.plot(x_grid, np.exp(best_score), label='bw=%.2f' % kde_model_best.bandwidth)
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029155227p:plain:w400

GridSearchといえば大変なイメージが強いが、KDE自体が高速なので割りと速くできる。
標本がバラけるような環境で困ったらこれ。

 

statsmodels

pip install statsmodels

見るのはKDEUnivariate。
データはfloat値である必要がある。
sampleがnotebookなのでそちらも参考に[ kernel_density ]

import statsmodels.api as sm

kde_model = sm.nonparametric.KDEUnivariate(data_list)
kde_model.fit()

plt.figure(figsize=(14,7))
plt.plot(kde_model.support, kde_model.density, alpha=0.5, label="statsmodels")
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029155758p:plain:w400

中身がFFTなだけあってシンプルなデータだとかなり早い。

 

pyqt-fit

インストールでこける

pip install pyqt-fit

 
Python3.6だと以下ERROR

    register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider)
AttributeError: module 'importlib._bootstrap' has no attribute 'SourceFileLoader'

pyenvでPython3.5.xにしたら上記ERRORは消える
 
それでも以下のようにpath.pyのバージョンでERRORになる

cannot import name 'path'

以下参考にpath.pyのバージョン指定してインストールしてnotebookとかPython周り再起動
python - PyQt_Fit: cannot import name path - Stack Overflow

sudo pip install -I path.py==7.7.1

 
インストールできてもimportでエラー

# from matplotlib.backends import _macosx
# RuntimeError: Python is not installed as a framework. The Mac OS X backend will not be able to function correctly if Python is not installed as a framework. 
# See the Python documentation for more information on installing Python as a framework on Mac OS X.
# Please either reinstall Python as a framework, or try one of the other backends.
# If you are using (Ana)Conda please install python.app and replace the use of 'python' with 'pythonw'.
# See 'Working with Matplotlib on OSX' in the Matplotlib FAQ for more information.

これはmatplotlib.pylotのsavefig()の「RuntimeError: Invalid DISPLAY variable」と同じ原因なので、以下のようにmatplotlibのpyplotを読み込む前にbackendをAggに変えて回避する。

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

 
Cythonが入ってないと以下Warningが出る

# Warning, cannot import Cython kernel functions, pure python functions will be used instead

KDEでは不要だが、使いたければpipで入れれば消える。

pip install cython

 
Kernelは4種類で内部パラメータもイジれる
bandwidthかcovarianceも["variance_bandwidth", "silverman_covariance", "scotts_covariance", "botev_bandwidth"]がある。
データポイントごとのbandwidthや重みも扱え、Kernelの足し合わせ方も選択できる。

from pyqt_fit import kde, kde_methods

kde_model1 = kde.KDE1D(data_list, bandwidth=0.1)
kde_model2 = kde.KDE1D(
    data_list,
    method=kde_methods.reflection,
    covariance=kde.variance_bandwidth(0.1, data_list))

plt.figure(figsize=(14,7))
plt.plot(x_grid, kde_model1(x_grid), label='bw={:.3g}'.format(kde_model.bandwidth))
plt.plot(x_grid, kde_model2(x_grid), label='variance bw={:.3g}'.format(kde_model.bandwidth))
plt.hist(data_list, alpha=0.3, bins=20, weights=weights)
plt.legend()
plt.show()

f:id:vaaaaaanquish:20171029160352p:plain:w400

裏で勝手にFFT使ってくれるのでかなり早い。


 

- 速度比較 -

以下で適当にn個作ったデータでKDE計算速度を比較する。
np.random.normalって中身ガウス分布じゃんパラメトリックでやれよ…という意見が来そうだけどやる。

x_t = 50 * np.random.rand() * np.random.normal(0, 10, n)
x_g = np.linspace(min(x_t), max(x_t), 100)

環境はPython 3.5 (3.6ではpyfitがうまくインストールできないため)
Macbook Pro 2.8GHz Core i7, memory 16G

nはデータ数
それぞれdefaultのパラメータで、100回KDE算出した際の平均速度(秒)を計算した。
ついでなのでsklearnのGridSearch(BW値のCross-validation)もやった

package n=100 1000 10000 100000 1000000
scipy 0.001563 0.003090 0.014148 0.142844 1.876725
statsmodels 0.000650 0.000775 0.002706 0.022177 0.297552
scikit-learn 0.000477 0.001233 0.017090 0.338675 11.294480
pyfit 0.000410 0.001728 0.016702 0.236698 2.491684
sklearn(gridsearch) 0.161637 0.285323 15.952579 None None

gridsearchは計算時間からこれ以上は無理そうなので断念。

シンプルな一次元データという事もあるが、statsmodelsクソ早い。
sklearnはtree-baseで最初早いけど、データ数の増加に弱い。


 

- おわりに -

速度ならstatsmodels、メソッドやパラメータの種類ならpyfit、GridSearchや他クラスの利用やclass拡張ならsklearn、scipyは簡易という感じ。

別にKDEについてこんなに詳しく調べるつもりはなかったけど、冒頭に出したk-nearest neighborのkd木の実装の論文を読んでると面白くなったので土日を使ってしまった。

次はkd木な構造の論文の記事を書く。

あと英語Wikiは丁寧すぎ
k-d tree - Wikipedia

構造的因果モデルの基礎

構造的因果モデルの基礎

 

GASでGithubの自分関連のReviewer情報を定期的にSlackにPostする

- はじめに -

GithubのPull Requestを大体1日以内に処理するルールだったのだが、Repositoryが増えて全然管理できなくなったりしたので、Reviewerに入っていてApprovedしてないものだけSlackに通知しようとこねくり回したGoogle Apps Script。

Lambdaとかを使う方が楽だけど、GASは金が掛からないしポチポチで時間トリガーやSlack Bot化を進められるのが良さ。

GASでSlack botは前に書いたので、Slack投稿までは下記で出来ている前提。

vaaaaaanquish.hatenablog.com


 

- GithubのPersonal Access Tokenの取得 -

Githubをブラウザから見て、SettingsからAccessTokenを取得する。

右上のアイコンからAccount -> Settings

f:id:vaaaaaanquish:20171007161923p:plain:w300:h200

上記画像の赤枠をポチポチすると取得できるので、文字列を控えておく。


 

- GAS -

メインとなるGithub周りのスクリプトを導入する前に日付のフォーマット関連の処理をよしなに処理出来るようにしておく(GASではdatetimeの文字列処理が面倒なため)。

日付周りの処理

新しいスクリプトを追加してそれぞれファイル分けできるのでやる
f:id:vaaaaaanquish:20171007183856p:plain:w350:h220


新しく作ったスクリプトファイルにisodate関数を作っておく。
isodateとして以下コードを引用。
iso8601.js/iso8601.js at master · shumpei/iso8601.js · GitHub

/*
 * JavaScript library for ISO-8601 datetime format.
 * Copyright: 2009, Shumpei Shiraishi (shumpei.shiraishi at gmail.com)
 * License: GNU General Public License, Free Software Foundation
 *          <http://creativecommons.org/licenses/GPL/2.0/>
 * Original code and license is:
 *   Web Forms 2.0 Cross-browser Implementation <http://code.google.com/p/webforms2/>
 *   Copyright: 2007, Weston Ruter <http://weston.ruter.net/>
 *   License: GNU General Public License, Free Software Foundation
 *          <http://creativecommons.org/licenses/GPL/2.0/>
 */
var isodate=function(){function e(e,t){t||(t=2);for(var r=e.toString();r.length<t;)r="0"+r;return r}var t=/^(?:(\d\d\d\d)-(W(0[1-9]|[1-4]\d|5[0-2])|(0\d|1[0-2])(-(0\d|[1-2]\d|3[0-1])(T(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?(Z)?)?)?)|(0\d|1\d|2[0-4]):([0-5]\d)(:([0-5]\d)(\.(\d+))?)?)$/;return{validate:function(e,r){var n=!1,a=t.exec(e);if(!a||!r)return a;if(r=r.toLowerCase(),"week"==r)n=0===a[2].toString().indexOf("W");else if("time"==r)n=!!a[15];else if("month"==r)n=!a[5];else if(a[6]){var s=new Date(a[1],a[4]-1,a[6]);if(s.getMonth()!=a[4]-1)n=!1;else switch(r){case"date":n=a[4]&&!a[7];break;case"datetime":n=!!a[14];break;case"datetime-local":n=a[7]&&!a[14]}}return n?a:null},parse:function(e,t){if(!e)return null;var r=this.validate(e,t);if(!r)return null;var n=new Date(0),a=8;if(r[15]){if(t&&"time"!=t)return null;a=15}else{if(n.setUTCFullYear(r[1]),r[3])return t&&"week"!=t?null:(n.setUTCDate(n.getUTCDate()-(7-n.getUTCDay())+7*(r[3]-1)),n);n.setUTCMonth(r[4]-1),r[6]&&n.setUTCDate(r[6])}return r[a+0]&&n.setUTCHours(r[a+0]),r[a+1]&&n.setUTCMinutes(r[a+1]),r[a+2]&&n.setUTCSeconds(r[a+3]),r[a+4]&&n.setUTCMilliseconds(Math.round(1e3*Number(r[a+4]))),r[4]&&r[a+0]&&!r[a+6]&&n.setUTCMinutes(n.getUTCMinutes()+n.getTimezoneOffset()),n},format:function(t,r){if(!t)return null;r=String(r).toLowerCase();var n="";switch(t.getUTCMilliseconds()&&(n="."+e(t.getUTCMilliseconds(),3).replace(/0+$/,"")),r){case"date":return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1)+"-"+e(t.getUTCDate());case"datetime-local":return t.getFullYear()+"-"+e(t.getMonth()+1)+"-"+e(t.getDate())+"T"+e(t.getHours())+":"+e(t.getMinutes())+":"+e(t.getSeconds())+n+"Z";case"month":return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1);case"week":var a=this.parse(t.getUTCFullYear()+"-W01");return t.getUTCFullYear()+"-W"+e(Math.floor((t.valueOf()-a.valueOf())/6048e5)+1);case"time":return e(t.getUTCHours())+":"+e(t.getUTCMinutes())+":"+e(t.getUTCSeconds())+n;case"datetime":default:return t.getUTCFullYear()+"-"+e(t.getUTCMonth()+1)+"-"+e(t.getUTCDate())+"T"+e(t.getUTCHours())+":"+e(t.getUTCMinutes())+":"+e(t.getUTCSeconds())+n+"Z"}}}}();

 

Githubからの情報取得

以下メインのスクリプト
userにReviewerかどうか知りたいユーザ(つまり自分)、TeamはそのRepository製作者やチーム名、githubAccessTokenに上記で取得したアクセストークンを入れる。
pullsNameListにはチェックしたいRepositoryの名前をリストで入れておく感じ。

// GithubのプルリクをチェックしてApprovedしてないやつを確認する
// 各設定
var user = "testersp";
var team = "vaaaaanquish";
var githubAccessToken = "hogehoge";
var baseUrl = "https://api.github.com/repos/" + team + "/{}/pulls";
var pullsNameList = ["pull-request-test"];

// pull requestsのURLを生成
var pullsUrlList = [];
for (var i = 0; i < pullsNameList.length; i++) {
  pullsUrlList.push(baseUrl.replace("{}", pullsNameList[i]));
}

// Httpオプションとヘッダ
var httpOptions = {
    method: "GET",
};
var httpOptionsReviews = {
    method: "GET",
    headers: {"Accept":"application/vnd.github.black-cat-preview+json"}
};

// main
function github_main() {
    // 土日は実行しない
    var today = new Date();
    if (today.getDay() == 0 || today.getDay() == 6) return;
    // GithubAPIを走査しSlack用の文字列allContentsを生成する
    var allContents = '';
    for (var i = 0; i < pullsUrlList.length; i++) {
        allContents += doOneRepository(pullsUrlList[i]);
    }
    return allContents;
}

// 1つのリポジトリから情報習得
var doOneRepository = function(githubUrl) {
    // リポジトリの基本情報を習得(プルリク取得)
    var response = getResponse(githubUrl, httpOptions);
    if (response === null || response.length == 0)
        return "";

    // 1リポジトリ分の文字列を生成  
    var allContents = '';
    for (var i = 0; i < response.length; i++) {
        var r = response[i];
        var createdAt = isodate.parse(r["created_at"]);
        var info = {};
        info.prNumber = r["number"];
        info.prTitle = r["title"];
        info.owner = r["user"]["login"];
        info.hourCreate = r["created_at"];
        // requested_reviewersに入っているユーザ
        // (recviewersに入っていてCommentもApproveもしていない)
        info.notApprovedPeople = getNotApproved(githubUrl+"/"+r["number"]+"/requested_reviewers");
        // 文字列生成
        var content = buildSlackPostingString(info);
        if(content.length > 1){
            allContents += (content + "\n");
        }
    }
    // 文字列を整形して返す
    allContents.replace(/.+\n$/g,"")
    var repository = githubUrl.split("/")[5];
    if(allContents.length > 1){
      return repository + '\n' + allContents + '\n';
    }else{
      return "";
    }
}

// requested_reviewersに入っている人を返す
var getNotApproved = function (GithubUrlReviews) {
    var res = getResponse(GithubUrlReviews, httpOptionsReviews);
    var result = [];
    for (var i = 0; i<res.length; i++){
      result.push(res[i]["login"]);
    }
    Logger.log(result);
    return result;
}

// HTTP GETリクエストを投げて返却値をJSONとして得る
var getResponse = function (url, options) {
    var urlToken = url + "?access_token=" + githubAccessToken;
    var response = UrlFetchApp.fetch(urlToken, options);
    if (response.getResponseCode() != 200)
        return null;
    return JSON.parse(response.getContentText());
};

// Slackに投稿する文字列の生成
var buildSlackPostingString = function (info) {
  if(info.notApprovedPeople.indexOf(user) > -1 ){
      var content = '> •  #' + info.prNumber + ' ' + info.prTitle;
      content += ' - @' + info.owner;
      content += '\n>     > CREATED: *' + info.hourCreate.split("T")[0] + '*';
      return content;
  }else{
    return "";
  }
}


具体的にはRepositoryを走査して、requested_reviewersエッジの情報に自分が入っているかをチェックするというもの。

土日は仕事をすべきではないので通知しないようにしている。

requested_reviewers自体はまだPreviewなので注意。以下Reference。
Review Requests | GitHub Developer Guide


以下のような状態でのみ反応する。
f:id:vaaaaaanquish:20171007184928p:plain:w350:h170

CommentかApproveするとrequested_reviewersには入って来ない。

 
この結果を冒頭に書いた記事内にある、GASとSlackの連携を行ったスクリプトに導入する。
Slack botをGASでつくる方法で一番楽そうなやつ - Stimulator

function postSlack(text){
  var url = "https://hooks.slack.com/services/hogehogehoge~";
  var options = {
    "method" : "POST",
    "headers": {"Content-type": "application/json"},
    "payload" : '{"text":"' + text + '"}'
  };
  UrlFetchApp.fetch(url, options);
}

function test(){
  s = github_main();
  postSlack("未読プルリク情報\n" + s);
}

以下のようにSlackに通知される。
f:id:vaaaaaanquish:20171007185438p:plain

GASの設定で適当に1日数回、時間トリガーで発火するようにしておけば大体大丈夫。
もしくはSlackのbotにして応答させれば良い。


 

- おわりに -

かつてrequested_reviewersが動かなかった頃は、review_comments_urlからcommentをそれぞれ見に行ってcommentしているかチェックしたりしていたんですが、Reviewer機能が実装されたことでこの辺のチェックも本当に簡単になりました。
良かったです。


GAS便利だけどちょっと書くのがしんどいです。

GASエディタもStylishCSS書き換えで少し見やすくしているけど、それでもやっぱりローカル開発環境で十二分に色々やりたいという気持ちになってしまう。
Stylish - ウェブサイト用カスタムテーマ - Chrome ウェブストア
Google Apps Script Dark - Monokai | Userstyles.org


出来ることなら使わずに金を払って既存サービスに任せたい所です。

本末転倒ですが以上です。


 

Workplace by Facebookを使いやすくするTips

- はじめに -

業務で社内SNSとしてWorkplaceを使っていた。

デフォルトのWorkplaceは「人気の投稿」や「チャット」が常に画面内に表示されており、見辛く辛い部分が多いので、それらを解決するTipsを書いておく。

Workplaceユーザ向け。


 

- Chrome拡張を導入する -

以下のChrome拡張によって、Workplaceの視認性が向上する。

chrome.google.com


以下GithubのReadmeに解説が載っている。

github.com


また以下記事の筆者が作成している。

qiita.com


導入するだけで変化が見られるので楽。
Workplaceを社内で使うなら導入しておいた方が生産性が100倍高い。

- Chrome拡張でさらにCSSを触る -

Chrome拡張によってブラウザで見ているWebサイトの見た目を書き換える方法はいくつかある。

私は適当にStylishを使っているので以下。

chrome.google.com

導入後は拡張から「スタイルを作る」

f:id:vaaaaaanquish:20171004114948p:plain:w400:h300

スタイル設定画面で見るべきは以下赤枠の4つ

f:id:vaaaaaanquish:20171004115308p:plain

左上から

  • スタイルの名前の設定と保存
  • CSSコード記述フォーム
  • 適応先
  • セクションの追加

まずスタイルの名前を設定し、適応先を「ドメイン上のURL:facebook.com」とする。

私がfacebook.comドメイン上に適応しているCSSは以下
ありとあらゆる情報を消して、自分が「お気に入り」に登録しているグループ以外表示しない設定。

._3pk8 {display: none;}
.sidebarMode .fbChatSidebar{display:none;}
#contentArea {
    width: 100%  !important;
    min-width: 502px;
}
#headerArea{
    width: 100% !important;
    min-width: 502px;
}
.sidebarMode #globalContainer{padding-right: 0px;}
._2yq ._2t-d, ._2xk0 ._2t-d{width: calc(100% - 100px);}
.timelineLayout #contentArea {
    width: calc( 100% - 330px) !important;
    min-width: 502px;
}
._5nb8 {
    width: calc(100% - 345px);
    min-width: 512px;
}
li._4lh .fbTimelineTwoColumn[data-type="s"] {
    width: calc(100% - 345px);
    min-width: 512px;
}
.timelineLayout #contentArea {
    max-width: 100% !important;
    width: calc( 100% - 150px)!important;
}
._4_7u {
    max-width: 100% !important;
    width: calc( 100% - 350px)!important;
}
.fbTimelineUnit {
    max-width: 100% !important;
    width: 100% !important;
}
.fbTimelineTwoColumn {
    max-width: 100% !important;
    width: 100% !important;
}
.timelineUnitContainer {
    max-width: 100% !important;
    width: calc( 100% - 25px)!important;
}
.fbTimelineUFI {
    max-width: 200% !important;
    width: calc( 100% + 20px)!important;
}
.letterboxedImage {
    max-width: 200% !important;
    width: 100% !important;
}
@media screen and (max-width: 1400px) {
    #globalContainer {
        width: calc( 100% - 100px) !important;
        min-width: 500px;
    }
}
@media screen and (min-width: 1401px) {
    #globalContainer {
        width: 1200px !important;
    }
}
@media screen and (max-width: 2000px) {
    .sidebarMode ._50tj{
        padding-right: 20%;
        padding-left: 20%;
    }
}
@media screen and (max-width: 1600px) {
    .sidebarMode ._50tj {
        padding-right: 10%;
        padding-left: 10%;
    }
}
@media screen and (min-width: 2001px) {
    .sidebarMode ._50tj{
        padding-right: 25%;
        padding-left: 25%;
    }
}
h4.navHeader {
    background-color: #999;
    padding: 3px;
}
h4.navHeader .sectionDragHandle {color: #fff;}
._1cwg {
    color: #fff;
    font-size: 9px;
}
._5vb_,
._5vb_ #contentCol {background-color: #ccc;}
h4.navHeader {padding: 3px;}
._1cwg {font-size: 9px;}
._bui .sideNavItem .imgWrap .img {display: none;}
#leftCol{position: fixed;}
._wmx .homeSideNav .navHeader .sectionDragHandle{color: black;}
._2xk0._5vb_.hasLeftCol #leftCol{width: 240px;}
html ._2xk0._5vb_.hasLeftCol #contentCol{margin-left: 240px;}
._wmx ._bui ._5afe .linkWrap{
    margin-left: 10px;
    max-width: 228px;
}
._55y4 ._bui .sideNavItem .linkWrap {margin-right: 2px;}
._wmx ._bui ._5afe .linkWrap.hasCount{max-width: 220px;}
._wmx .homeSideNav#pinnedNav ._bui ._5afe{padding-left: 20px;}
#pagelet_company_logo{display:none;}
#appsNav {display: none;}
#navItem_230259100322928{display: none;}
#navItem_4748854339{display: none;}
#navItem_1434659290104689{display: none;}
#navItem_2344061033{display: none;}
#workGroupsTeamNav{display: none;}
#workGroupsTeamNav{display: none;}
#workGroupsAnnouncementNav{display: none;}
#workGroupsFeedbackNav{display: none;}
#workGroupsSocialNav{display:none;}
._wmx._wmx .homeSideNav .navHeader{display:none;}
._55y4 ._bui .sideNavItem .linkWrap{font-size: 13px;}
._wmx._wmx ._bui ._5afe .linkWrap {max-width: 100%;}
#rightCol ._5zny{display:none;}
div._4-u3._5dwa._5dwb{
    display:none;
    padding:0px;
}
#rightCol ._i7m{display:none;}
.uiBoxWhite{border:0px;}

 
上記CSSだけでは所謂本家のFacebookページに弊害が出たり、グループページで適応されない場合があるので、グループページに対してのみ以下を設定する。

先程の「他のセクションを追加」して、以下のようにコード2を設定。

f:id:vaaaaaanquish:20171004120326p:plain:w400:h300

適応先は「正規表現に一致するURL:.*facebook.com/groups/.*」とし、グループの情報ページに適応させている。

#rightCol ._4-u2._3-96._4-u8:nth-of-type(n+2) {display: none;}
#rightCol {
    width: 100% !important;
    min-width: 502px;
    float:left;
    display:block;
}
#pagelet_rhc_footer {display: none;}
.fixed_elem {
    position: absolute !important;
    top: 0px !important;
}
._5ks6{
    height:40px;
    width:40px;
    padding:0px;
}
#groupsRHCMembersFacepile,
.profileBrowserGrid,
._5ks4{
    height:40px;
    width:100%;
}
#groupsNewMembersLink div:nth-of-type(3){display:none;}
#groupsNewMembersLink{display:none;}

 
上記に加えてグループフィードの中でも適度にCSSを書き換えて閲覧。
新しいセクションを作り、適応先を「正規表現に一致するURL:.*facebook.com/(?!.*groups).*」とすることで、グループフィードのページにCSSを適応。

#rightCol{display:none;}
.timelineLayout #contentArea{width:100% !important;}
._4gt0{display:none;}
.fbTimelineStickyHeader .stickyHeaderWrap{display:none;}


ほとんどの情報が消えるので気になるなら所々display:noneにしてあるところを外していく。


 

- おわりに -

これで大体Workplaceでやっていたことが記事にできた。

Workplaceは、デフォルトだとFacebookみたいで本当に見にくいサービスだが徐々に改善が見られるので今後に期待という感じである。

日々機能が増えていくのはありがたいが、UI/UXをまず見直してほしい…


APIでの操作の記事も書いてある。

vaaaaaanquish.hatenablog.com

 

Workplace by FacebookのGraph APIによる投稿、情報取得、DMの操作メモ

- はじめに -

弊社では、WorkplaceなるFacebookを模した社内SNSを利用している。
1年弱使ったが、非常に出来が良くなりつつある社内ツールである。

見やすく扱いやすくするTipsも書いたくらい使っている。
vaaaaaanquish.hatenablog.com

 
WorkplaceにはGraph APIが用意されており、それによる様々な自動化が可能である。

本記事ではPython 3.xを用いながら、WorkplaceAPI周りのメモをまとめておく。

Workplaceリファレンスは以下だが、現状あまり親切なリファレンスではない。
Reference - Workplace - ドキュメンテーション - 開発者向けFacebook

WorkplaceのGraph APIは多分実装をFacebookからそのまま持ってきており、FacebookAPIと同じ使い方が動く場合が多々あるため以下のリファレンスが時に参考になる。
Graph API Reference - Documentation - Facebook for Developers

 
Pythonではrequestsモジュールを基本的に使う。入って無ければpipで導入。

sudo pip install requests

 

- アクセストークンの取得と利用 -

アクセストークンは管理者権限のあるアカウントによって取得する。
例えば社内であれば偉い人が持っている。

私自身は管理者になった経験がないが、多分Facebookと同様にDeveloperページの右上からログインし、社内ダッシュボードから外部アプリを発行してもらい、そのアクセストークンとコミュニティIDを使う。

f:id:vaaaaaanquish:20170929062208p:plain:w250:h150

 
Graph APIには、App Access Token(access_token)とMember Access Token(impersonate_token)が存在する。

Appのトークンは、「Permission情報の取得、変更」や「管理者の取得」「グループ情報の取得」「メンバー情報の取得」「Memberのトークンの取得」などが行えるものである。一般的なSNSAPIとして、投稿や削除を行うには「Memberのトークンを取得」しMemberのトークンによってMemberのアカウントを操作する。

Appのトークンは無制限に使えるが、Memberのトークンは24時間でAppトークンによって再発行する必要がある。

f:id:vaaaaaanquish:20170929070101p:plain:w500:h350
トークンの扱い

上記の図では、社内の特定人物のimpersonate_tokenを自由に取得して色々できるように(例えば社長のアカウントから投稿できるように)見えるが、その点は管理者によるaccess_tokenの権限設定によってコントロールするのがWorkplaceAPIの思想だと思われる。
多分、中央管理する管理者がコントロールしやすいように。

 
前述した通り、Pythonではrequestsモジュールを基本的に使う。入って無ければpipで導入。

sudo pip install requests

参加している全ユーザの名前とidの取得

impersonate_tokenを取得せず、その企業に参加しているユーザのidと名前、Adminかどうか取得する。
これらはAppのaccess_tokenだけでもできる。

import requests
import json

TOKEN = "管理者より得たAppのAccessトークン"
GRAPH_URL_PREFIX = "https://graph.facebook.com/"
COMMUNITY_ID = "管理者より得たコミュニティID"

headers = {'Authorization': 'Bearer ' + TOKEN}
graph_url = GRAPH_URL_PREFIX + COMMUNITY_ID + "/members"
result = requests.get(graph_url, headers=headers)
result_json = json.loads(result.text)

この状態で25件までユーザ情報の取得が可能。

それ以上の情報を得るには返却された結果の['paging']['next']に入っているURLを叩く。
以下のようなコードを追記すればよい

while(1):
    result = requests.get(result_json['paging']['next'], headers=headers)
    result_json = json.loads(result.text)
    if "next" not in result_json['paging'].keys():
        break
    print(result_json['paging']['next'])

また、一回で取得できる量や、内容はパラメータによって設定できる。
例えばユーザが1000人程でidのみ欲しい場合は、whileで回さず以下のようなURLを叩くと一発で返ってくる。

graph_url = GRAPH_URL_PREFIX + COMMUNITY_ID + "/members?fields=id&limit=1500"

 

メンバーのimpersonate_tokenの取得

ユーザのidは上記方法で取得するか、ユーザのプロフィールページのURLにidというパラメータで記載されている。
例:https://hogehoge.facebook.com/profile.php?id=100000000000081

そのidに対してimpersonate_tokenを取得するには以下のURLをフィードの取得と同じようにgetで叩く。

import requests
import json

TOKEN = "管理者より得たAppのAccessトークン"
GRAPH_URL_PREFIX = "https://graph.facebook.com/"

headers = {'Authorization': 'Bearer ' + TOKEN}
graph_url = GRAPH_URL_PREFIX + "100000000000081" + "?fields=impersonate_token"
result = requests.get(graph_url, headers=headers)
result_json = json.loads(result.text)

print(result_json["impersonate_token"])

このimpersonate_tokenを利用してユーザに成りすます形で投稿や情報取得を行う事ができる。

 

メンバーの情報の取得

上記のidには、impersonate_token以外にEmailや電話番号、姓名、部署やプロフィール、ヘッダ画像といった情報も紐付いており、それらもAppのaccess_tokenによって取得が可能である。
詳細なフィールドは以下のReferenceのFieldsを参考に。
Member - Workplace - ドキュメンテーション - 開発者向けFacebook

 
例えば参加しているユーザの「Email」「名字」「impersonate_token」「画像URL」を取得したい場合は、上記のimpersonate_tokenを取得する時のコードを参考にURLのフィールドを以下のように変更する。

graph_url = GRAPH_URL_PREFIX + "100000000000081" + "?fields=impersonate_token,email,first_name,picture"

これによりユーザの情報取得が可能である。

上記参考にしているReferenceのURLのEdges 以降はimpersonate_tokenが必要になってくる。


 

- impersonate_tokenによる情報取得 -

impersonate_tokenを取得する事で、そのユーザに成りすます形でWorkplace上の情報の取得編集が可能である。

以降は上記したimpersonate_tokenを取得する方法24時間以内に取得したimpersonate_tokenがある状態で進める。

 

ユーザプロフィール画面のフィードの投稿取得

WorkplaceにはFacebook同様、自分のフィードが用意されている。

f:id:vaaaaaanquish:20170929104457p:plain:w500:h300

最もシンプルに、id=100000000000081のフィードを取得するには以下

import requests
import json

TOKEN = "取得したimpersonate_token"
GRAPH_URL_PREFIX = "https://graph.facebook.com/"

headers = {'Authorization': 'Bearer ' + TOKEN}
graph_url = GRAPH_URL_PREFIX + "100000000000081/feed"
result = requests.get(graph_url, headers=headers)
print(json.loads(result.text))

上記コードにより、以下のような結果が返ってくる。

{'data':
  [
    {'message': 'これはテストです',
     'created_time': '2017-09-29T01:36:19+0000',
     'id': 'この投稿のid'}]}

 
このPostには様々なFieldが付随している。詳細は以下。
Post - Workplace - ドキュメンテーション - 開発者向けFacebook

例えば投稿に付随する「permalink_url」や「画像」の情報を取得するには以下のようにfieldsパラメータをつけたURLを叩く。

graph_url = GRAPH_URL_PREFIX + "100000000000081/feed?fields=permalink_url,image"

 
上記方法では25件まで投稿の取得が可能である。
limitは上記access_tokenによる情報取得の時同様limitパラメータを付ける。
また、Feed全般における時系列情報についてはsince等のパラメータの利用も可能である。

以下は10日以内の情報を最大100件取得できるURLを生成している例である。

import datetime

DAYS = 10
SINCE = datetime.datetime.now() - datetime.timedelta(days=DAYS)

graph_url = GRAPH_URL_PREFIX + "100000000000081/feed?fields=permalink_url,image&limit=100&since="
graph_url += SINCE.strftime("%S")

 
パラメータについてはWorkplaceのページには一切解説がなく、FacebookのGraph APIを参考に試行錯誤していくしかない状態である。
Graph API Reference Post /post - ドキュメンテーション - 開発者向けFacebook

中には機能的に動作しないパラメータもあるので注意が必要。

 

ユーザのプロフィール画面の投稿に対するコメントの取得

WorkplaceではFacebook同様、投稿に対するコメントと、そのコメントに対するコメントの二種類がある。

f:id:vaaaaaanquish:20170929111514p:plain:w500:h200

面倒だが、現状「投稿の取得」「投稿のコメントの取得」「投稿のコメントへのコメントの取得」はそれぞれAPIを叩いて行う必要がある。


投稿にはそれぞれ独自のidが振られている。
上記のフィード取得の方法ではレスポンスに['data']['id']が含まれているのでそれ。

また投稿の時間部分のリンクをクリックすると投稿のページに飛べるが、このリンクには以下のようにstory_fbidとidパラメータがあり、こちらをアンダースコアで繋ぎ合わせると投稿のidが作成できる。

f:id:vaaaaaanquish:20170929112642p:plain
例:https://hogehoge.facebook.com/permalink.php?story_fbid=300000000000003&id=100000000000001&pnref=story
上記例における投稿のid:100000000000001_300000000000003

この投稿のコメントを取得するには以下

import requests
import json

TOKEN = "取得したimpersonate_token"
GRAPH_URL_PREFIX = "https://graph.facebook.com/"

headers = {'Authorization': 'Bearer ' + TOKEN}
graph_url = GRAPH_URL_PREFIX + "100000000000001_300000000000003" + "/comments"
result = requests.get(graph_url, headers=headers)
print(json.loads(result.text))

結果としてはこんな感じ

{'data': 
  [
    {'created_time': '2017-09-29T02:11:13+0000',
     'from': {'name': 'ユーザ名', 'id': '投稿したユーザのID'},
     'message': 'テストに対するコメントに対するコメントです', 'id': 'コメントのid'}],
     'paging': {'cursors': {'before': 'WT~', 'after': 'WT~'}, 'next': URL}}

返却されたデータの中に「コメントの投稿id」があるため、それを利用して「コメントへのコメント」を取得する形になる。
やり方は投稿のコメントの取得方法と同じ。

上限は25件のため、アクセストークンによる情報取得と同じようにlimit上限をあげたり、pagingのnextに書いてあるURLを利用して上限の次の情報を取得する。

 

グループ情報の取得

Workplaceにはプロジェクトやチーム、部署ごとにGroupを作って、その中にスレッドを立てて投稿できる機能がある。

f:id:vaaaaaanquish:20170929114735p:plain:w300:h320


グループの名前やidはimpersonate_tokenなしでもaccess_tokenのみで取得できる。
しかし、Workplaceには「参加した人しか見れない非公開グループ(CLOSED)」「グループ一覧にも表示されない秘密のグループ(PRIVATE)」を設定する事が可能で、それらはaccess_tokenでは取得できず、そのグループに参加しているユーザのimpersonate_tokenのみで取得が可能となる。


全てのグループ情報を取得するには、フィードの取得同様getで以下のURLを叩く。

GRAPH_URL_PREFIX = "https://graph.facebook.com/"
COMMUNITY_ID = "管理者より得たコミュニティID"
graph_url = GRAPH_URL_PREFIX + COMMUNITY_ID + "/groups"
||< 

以下のような、グループの名前とグループid、そのグループの状態を含めた結果が帰ってくる。
>|json|
{'data':
  [
    {'name': 'test',
     'privacy': 'OPEN',
     'id': '1000000000000006'},
    {'name': 'hogehoge',
     'privacy': 'CLOSED',
     'id': '1000000000000001'},
    {'name': 'TeamA',
     'privacy': 'SECRET',
     'id': '1000000000000003'}],
 'paging': {'cursors': {'before': 'QV', 'after': 'QV'}, 'next': URL}}

アクセストークンによる情報取得と同様、デフォルトでは一度に取得できる件数が25なので、limitを上げるかnextを見てさらに追加で情報を取得する。

 
グループ情報に付随するFieldにはiconやDescription、グループ管理者等があり、以下を参考にフィールドを追加する形で取得が可能である。
Group - Workplace - ドキュメンテーション - 開発者向けFacebook
 
特定の1グループに関する周辺情報を取得するには、グループidに対してリクエストを叩く。
グループIDは上記のグループリストのような形で取得する他、グループページのURLからも判断できる。
例:https://hogehoge.facebook.com/groups/1000000000000006/
例のURLのグループID:1000000000000006

例えば上のグループには以下のようにfieldsを指定したURLを叩く。

GROUP_ID = "1000000000000006"
graph_url = GRAPH_URL_PREFIX + GROUP_ID + "?fields=cover,description,name"

上記例ではカバー画像のURLやグループの名前、説明を取得している。
レスポンスが大きくなりそうな情報(メンバーのリストやファイル情報)については、Edge情報として下記のやり方で取得する。

 

グループの関連情報の取得

上記のグループの情報において、Edgeになる情報は別の形で取得する。
以下のEdgeを参考。
Group - Workplace - ドキュメンテーション - 開発者向けFacebook

取得するにはURLの末尾を以下欲しい情報に合わせて叩く

  • /admins グループ管理者
  • /albums グループに投稿された画像群
  • /docs グループに投稿された文書
  • /events グループで開催されているイベント
  • /files グループに投稿された上記以外のファイル
  • /member_requests 現在のメンバーリクエスト(鍵グループのみ)
  • /members メンバーのリスト
  • /moderators グループ作成者 
  • /feed (後述)

例えばGROUP_ID = "1000000000000006"に対してグループの管理者が知りたい場合は、以下のようなURLを叩く。

GROUP_ID = "1000000000000006"
graph_url = GRAPH_URL_PREFIX + GROUP_ID + "/admins"

一回のリクエストにおける情報取得件数のデフォルト値は25件であり、アクセストークンによる情報取得と同様に、大きなlimitなどを利用し情報を取得する。

 

グループのフィードへの投稿の取得

グループフィードへ投稿する。
以下のReferenceのEdge情報を参考にする。
Group - Workplace - ドキュメンテーション - 開発者向けFacebook


グループへの投稿のidは、上記グループフィードの情報取得からも得られるが、URLからも判別できる。
例:https://hogehoge.facebook.com/groups/1972434329706466/permalink/1000000000000006
例のURLのグループID:1000000000000006


基本的には個人フィードへの投稿の取得と同じである。
グループのフィードの投稿はidに対して/feedを付ければ取得できる。

例としては以下

GROUP_ID = "1000000000000006"
graph_url = GRAPH_URL_PREFIX + GROUP_ID + "/feed"

個人フィードへの投稿の取得と同様に、デフォルトでは25件まで投稿の取得が可能である。
limitパラメータ、sinceパラメータの利用も可能である。

  

投稿に対するコメントの取得

ユーザのプロフィール画面の投稿に対するコメントの取得と同様にフィードの投稿等のコメントも取得可能であるが、グループへの投稿と個人フィードの投稿に付くIDの形式が違うので注意する必要がある。


取得の方法は同じで、投稿ID + "/comments"のURLを叩く。

graph_url = GRAPH_URL_PREFIX + "100000000000001" + "/comments"

コメントのコメントの取得の方法もユーザのプロフィール画面の投稿に対するコメントの取得に書いてある方法と同様の形で取得できる。

 

投稿に対するLike数やメディア情報の取得

WorkplaceにはFacebookと同じくコメントに「ライク」「リアクション」を付ける事ができる。
また、投稿に対して動画や画像、ワードファイル等を直接貼り付ける事ができる。


コメントに付随するそれらの情報を取得するには、上記の投稿へのコメントの取得と同じように投稿idに対するEdgeにそれぞれ欲しい情報の名前を付けたURLを叩く。

以下のReferenceのEdge情報を参考にする。
Group - Workplace - ドキュメンテーション - 開発者向けFacebook


例えば、投稿idが100000000000001の投稿に対してライクした人を知りたければ以下のURLを叩く。
(グループへの投稿と個人フィードの投稿に付く投稿idの形式がかなり違うので戸惑うがやり方は同じ)

graph_url = GRAPH_URL_PREFIX + "100000000000001" + "/likes"

これでLikeしたユーザの名前とidのリストを取得できる。

リアクションした人のリストは/reactions、動画や文書添付情報は/attachmentsを叩く。

 

ユーザのDMの取得

ユーザのDMの取得には、ユーザIDとimpersonate_tokenを利用する。
一応Referenceには、impersonate_tokenが示す当人のDMしか取得できないことになっている。
Graph API Reference: User conversations - Documentation - Facebook for Developers


ユーザIDはユーザのimpersonate_tokenの取得時にも出てきた個人のID。

GRAPH_URL_PREFIX + USER_ID  +"/conversations?fields=messages{message,attachments}

上記にはfieldsパラメータを付与しているが、現時点ではそれらが無いと取得できなかった。

リファレンスでは、Creating、Updating、Deletingも「You can't perform this operation on this endpoint.」となっているため、現状DMの操作は取得だけでDMを送る、更新する、削除するといった操作は記事執筆時点ではできない


 

- impersonate_tokenによる投稿 -

impersonate_tokenを取得する事で、そのユーザに成りすます形でWorkplace上への投稿が可能である。

以降は上記したimpersonate_tokenの取得の方法で24時間以内に取得したimpersonate_tokenがある状態で進める。

 

プロフィール画面のフィードへの投稿

requestのPOSTメソッドを使ってユーザとして投稿する。


ユーザIDが100000000000081のユーザのフィードに投稿するには以下のようにIDのfeedエッジに対してPOSTを叩く

import requests
TOKEN = "取得したimpersonate_token"
GRAPH_URL_PREFIX = "https://graph.facebook.com/"
headers = {'Authorization': 'Bearer ' + TOKEN}

USER_ID = "100000000000081"
graph_url = GRAPH_URL_PREFIX + USER_ID + "/feed"
data={
    "message":"hello",
    "link":"https://developers.facebook.com/docs/workplace/custom-integrations/apps"}
requests.post(graph_url, headers=headers, data=data)

f:id:vaaaaaanquish:20170929145931p:plain:w400:h280

投稿者名の隣にtool_appなどApp名が表示されるので、APIから投稿しているかどうかがわかる。

また、POSTには以下のような情報を付与できる。
Post - Workplace - ドキュメンテーション - 開発者向けFacebook

例えば以下

  • message 本文
  • formatting 本文をマークダウンにするか普通のテキストにするか
  • link リンク情報
  • permalink_url 投稿の固定リンク
  • picture 添付する画像のURL
  • place 位置情報
  • poll 投票
  • properties 動画やドキュメントの詳細
  • type 添付ファイルのタイプ
  • to 投稿に紐づけるユーザ
  • with_tags ポストにつくタグ

よしなにdataの中身に付ければつく。

 

グループへの投稿

上記のフィードへの投稿のUSER_IDがGROUP_IDに変わるだけ

graph_url = GRAPH_URL_PREFIX + GROUP_ID + "/feed"

 

投稿へコメントする

全ての投稿には投稿IDが付いており、そのIDのcommentsエッジに対してポストする。
投稿IDは、上記プロフィールフィードのコメント取得グループフィードのコメント取得を参照。


上記のフィードへの投稿を参考にポストする。

graph_url = GRAPH_URL_PREFIX + 投稿のID + "/comments"
requests.post(graph_url, headers=headers, data={"message":"hello"})

f:id:vaaaaaanquish:20170929151258p:plain:h220:w450

投稿の投稿にコメントする場合も同様に、投稿idのcommentsエッジに対してpostする。

 

投稿のフォーマット

最近やっとAPI経由での投稿でMarkdown形式やユーザへのリプライが可能になった。
Workplace公開当初、いち早く弊社は導入しAPIも真っ先に触ったがこの辺りが真っ当に動かず本当にアレだった。


フィードへの投稿を参考に、message内で@[ユーザID]とする事でリプライ、formattingにMARKDOWNを指定する事でマークダウン形式での投稿ができる。

使える構文は以下のReferenceページが参考になる。
Post - Workplace - ドキュメンテーション - 開発者向けFacebook

例を示すとこんな感じ

data = {
    "message":"[@100013051545981]\n``` code block ```",
    "formatting":"MARKDOWN"}
requests.post(graph_url, headers=headers, data=data) 

また、最近ではpoll情報をdataに追加することで投票投稿なども作れるようになった。

f:id:vaaaaaanquish:20170929152545p:plain:h200:w400


 

- おわりに -

WorkplaceのGraphAPIは公開当初こそ使い物にならないレベルだったが、最近はよくなっている。

通知の取得とかDM操作ができない、Likeやリアクションができない、ビデオ配信機能もあるのに取得できない…みたいな不満は多々あって、一応Workplaceの公式ユーザページで文句は出ているが・・・


以前技術ブログじゃないほうで社内SNSのススメという記事を書いたが、この時よりは幾分かマシになっていると思う。

vaaaaaanquish.hatenadiary.jp


更新にもスピード感があるし、それだけエンジニアが投入されているという証だろうか。