gepuro.net
gepulog

データ分析エンジニアによる備忘録的ブログ

『詳解 Apache Spark』出版記念イベントに参加しました

最近はSparkに触れていなかったのですが、日本語の情報も豊富になってきたのを感じました。 自分も書きたい。 ブログネタでやるかなあ。と思った一日でした。

データ分析業界の理想と現実 - Spark普及の歴史を添えて(下田倫大さん)

  • Spark登場時に私は夢とロマンを見ました!
  • 現実は厳しい・・・。
  • ガンガン使おう
  • 2013年: 大規模データ処理といえばHadoopで集計
  • 2014年: Sparkとの出会い, インドで使ってた
  • 第一印象: Python, Rのインターフェースがある!
  • 2015年: ブレインパッド社としてビジネスモデル的にSparkのメリットが大きくない
  • コンサル/SI は新しい技術導入が遅くなりがち
  • ニーズが必要
  • DeltaCubeという自社開発プロダクトで導入
  • 案件が効率化からイノベーションに変化

データマネジメントツールDeltaCubeでのSpark利用(師岡一成さん)

  • 2014年に触り始めた
  • 2015年に本格的に
  • DeltaCube: プライベートDMPのログからセグメントを作成ツール
  • 2014年に開発が始まり、リリースまで2年ぐらいかかった
  • 初期
    • 行動ログを手動で検索して、セグメントを作成
    • データサイズは数TB, presto, impalaを使った
  • 中期(2015年) * 機能的にマニアック路線
    • 広告運用の手間がかからないようにしたい
    • 自動でユーザをクラスタリング(DeltaCube)
    • Rtosterで自動ABテスト
    • ETL, MLlib
  • 現在
    • YARNとPrestoの二つはいやだけど、統一できず・・・
    • EMR上にPrestoクラスタを稼働
  • Sparkよ流行ってくれ

Dynamic Resource Allocation in Apache Spark(今井雄太さん, @imai_factory)

  • Sparkとの出会い
    • Amazon KinesisのConsumerとして
  • Spark運用の話
  • ワードカウントの例(scalaで)
  • DAG Scheduler
    • sc.parallelize()でRDDが出来る、分散して保持されるオブジェクト
    • Array⇒ParallelCollectionRDD⇒MapPartitionsRDD⇒ShuffledRDD⇒Array
  • Tasks > Executors
    • Executorsの数をダイナミックに変えてくれる
    • Tasksの応じて、増やしたり、減らしたり
  • Dynamic Resource Allocationのよさ
    • 投げられたJobに対して、動的にExecutorsを変えて、速度を上げる
    • 消さないとリソースが無駄になるので、消そう

Help me! Help me with DNN in Spark!!(石川 有さん)

  • Spark2.0に向けてDNNの拡張を進めるとあったが、見送られた
  • 誰か助けて!
  • MultilayerPerceptronClassifier
    • Spark1.5
    • L-BFGS
    • Sigmoid, Softmaxのみ
  • Regression対応が必要
    • ReLU, 恒等写像
    • L-BFGSではなくSGDに最適化アルゴリズムを変更する必要がある
    • AdaGrad, Adamなどを実装できるように更新処理を再設計
  • 現在のSGDは非効率で精度が悪い
    • Parallelized SGDとして再実装する必要がある
  • Drop-outもできない
  • CNNのような多次元配列の入力をサポートしていない
  • Bayesian Optimizationなどの自動チューニングアルゴリズムを追加するべき
  • 既存のPublic APIを変更するのを嫌う風土

scalatestでSpark StreamingのUnitを書く話(田中裕一さん)

  • 100万人のデータ分析(Sparkを含む)
  • API 300億回/日でもSparkが使える
  • Spark as a Service
  • StreamingのUnitが面倒
    • アプリケーション側で状態を保つため、Unitが組みづらい
    • Streamingは外部との接続が前提なためUnitが書きにくい
    • 複数のmicro-batchの集計結果などの書き方が難しい(window処理など)
    • やったこと
      • Unit側でStream作成、Unitでテストデータ作成⇒Streamへ、処理結果の出力
    • 処理部分と接続部分は切り分けて書く(出力部分の切り分けた方が良い)
    • どうやってStreamを渡すか
  • SparkでTestを書こう
  • 簡単に書けるけど、構造化を意識しないと汚いコードになっちゃうので注意

spark.ml の API で XGBoost を扱いたい!(小宮篤史さん)

  • SmartNewsのエンジニアをしてます
  • XGBoostを知っているひと⇒ 少数
  • Spark上でXGBoostを使ったことあるひと⇒ 会場でほぼいない
  • SparkXGBoost
    • pure Scalaで実装
    • 開発が活発ではない
  • xgboost4j-spark
    • DMLCが提供する公式のSpark integration
    • RDDのみ、DataFrameに非対応

Sparkで始めるお手軽グラフデータ分析(加嵜長門さん)

  • Hadoop/Spark Conference Japan 2016でのアンケート
  • GraphXの利用ユーザが30人!(Spark SQL/ DataFrameは252人)
  • GraphFramesの登場
  • GraphXとDataFramesの統合
  • 1人3冊買えば、続編が出るかも!
  • グラフじゃないとできないの?
    • 多くの場合は他の選択肢がある
  • いろんな視点を持つことが大事
  • 社内営業をする時に、絵で説明できて良いこともある
  • リコメンド
    • ユーザ同士、商品間
  • マーケティング
    • ネットワークビジネス
    • バイラルマーケティング
    • 口コミを使ったマーケティング
      • 影響力の強いユーザの抽出
  • 不正検知
    • 偽装保険金詐欺
      • 少人数で何度も事故にあっているなんてことが・・・
    • クレジットカードの詐欺
      • 住所や電話番号を少人数で使いまわす
  • 会社間のお金の流れ
  • グラフDB
    • Neo4J, Titan
    • 分散処理による高スループット
  • グラフ処理系
    • GraphLab
    • グラフに特化しない汎用的なデータ構造
    • 表形式やベクトルとシームレスに結合できる
  • RDDから1行でグラフを作れる!

さいごに

アフィリエイトを張っておきます。

jqコマンドで正規表現を使う

日常的に利用しているjqコマンドですが、最新バージョンを入れれば正規表現が使えると知ったので、インストールしました。 OSはubuntu 14.04です。

基本的には、 https://stedolan.github.io/jq/download/に従えばよいですが、正規表現を利用するには外部ライブラリのインストールが必要なようです。

下のようにしてインストールした。余分なライブラリが入っているだろうが、調査するのが面倒だったので、apt-cache search onigでヒットしたものを全部突っ込んだ。

sudo apt-get install libjruby-joni-java libonig-dev libonig2 libonig2-dbg lua-rex-onig lua-rex-onig-dev

あとは、公式の通りにインストールするだけ。

git clone https://github.com/stedolan/jq.git
cd jq
autoreconf -i
./configure --disable-maintainer-mode
make
sudo make install

手元の環境では、./configure --disable-maintainer-mode --prefix=/usr/local/をしておいたり、pacoを使ってインストールしています。

Dockerでapacheを使う

忘れそうなので、メモしておく。

ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2

CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]

Python3のFlaskをwsgiで動かす

自分の環境ではdocker上で動かしていましたが、本筋には影響を与えないので省略します。

Flaskのドキュメントであるmod_wsgi (Apache)に書いてあるし、Python2の頃は試したことあるから、余裕だろうと油断していた。

sudo apt-get install -y apache2 \
    apache2-mpm-prefork \
    apache2-utils \
    libexpat1 \
    ssl-cert \
    libapache2-mod-wsgi-py3

上のようにインストールした。 また、

/etc/apache2/sites-available/000-default.conf を

<VirtualHost *:80>
ServerName example.com
WSGIDaemonProcess app user=www-data group=www-data
WSGIScriptAlias / /app/app.wsgi
<Directory /app>
WSGIProcessGroup app
WSGIApplicationGRoup %{GLOBAL}
# Order deny,allow
# Allow from all
Require all granted
</Directory>
</VirtualHost>

にします。dockerで動かすだけなので、ファイルを直に編集しています。

ハマりポイント1

libapache2-mod-wsgi-py3をインストールする。

パッケージ名から想像できるが、wsgiはPython3とPython2系で異なる。

ハマりポイント2

apacheの設定では、 Require all grantedを使う。

Python2系では、 Order deny,allow Allow from all で良かった。

http://stackoverflow.com/questions/30642894/getting-flask-to-use-python3-apache-mod-wsgiに救われました。

ちなみにPyttho2系の時は、Flaskでhttpsを使うに書きましたので参考にどうぞ。

ユーザのアクセス元の街を可視化してみる

当ブログのユーザのアクセス元を可視化しました。

アクセス元のipアドレスからhttps://freegeoip.net/より提供されるAPIを用いて、どの街かを特定しました。

観測期間は適当です。(freegeoipでデータを取得し始めてからで、過去に遡るのは面倒だった)

以下が結果です。パレート図で描きました。

久しぶりにパレート図を見たわけですが、円グラフに比べて見やすいなあと感じます。もっと流行るとイイなあ。

それにしても、mountain viewって何だろうか。

最後にコードです。

cat data/* |
jq -r '
select(.site=="blog.gepuro.net") |
select(.geo.city != null) |
[.cid, .geo.city] |
@csv' |
sort |
uniq |
header -a '"cid","city"' |
q -H -O -d ',' '
select
case city
when "" then "unknown"
else city
end as city
, count(1)  as count
from -
group by city
order by 2 desc
' |
Rio -r -e 'df %>% mutate(crank=1:n())' |
Rio -r -e 'rbind(df[df$crank<10,c("city","count")], data.frame(city="other", count=sum(df[df$crank > 10,]$count)))' |
Rio -e 'library(qcc); library("devtools"); source("https://gist.githubusercontent.com/abikoushi/722ce195fb70df5cc679/raw/fd434a74d86ab3feb415da33b3f8abb4dd03355f/ParetoChart2.r"); city <- df$count; names(city) <- df$city; png("count_city.png"); pareto.chart2(city, unsorted=TRUE); dev.off()'

Rioを使ったら、読みにくくなってしまった。 qコマンドやjqコマンドと違って、改行を許してくれないんだよなあ。。。 (自分で直せって話だな)

シェルスクリプトでアクセスログを用いてページ間の類似度を求める

協調フィルタリングです。

協調フィルタリングに関しては、「協調フィルタリングについてまとめてみた。」が詳しく書かれており参考になります。

データセットは、いつものようにwebビーコンのログです。

使用したコマンド

  • jq
  • q
  • sed
  • awk
  • mcmd
  • http://datascienceatthecommandline.com/
  • など

実際のコマンド

もう少しクールに書けるような気がします。

cat data/* |  # jsonからデータを取り出してcid, urlのデータを得る
jq -c -r 'select(.site=="blog.gepuro.net")| [.cid, .url] | @csv' |
sed -e 's/\?.*"$/"/' |
grep "http://blog.gepuro.net/archives/" |
sed -e 's/http\:\/\/blog\.gepuro\.net\/archives\///' |
header -a '"cid","url"' | # cidごとにurlのビューをカウントして横持ちにする
q -H -O -d ',' "
select cid, url, count(1) as view
from -
group by cid, url
" |
mcross k=cid f=view s=url v=0 |
cat > tmp/cross.csv

cat tmp/cross.csv | # urlごとのコサイン類似度を求める
msim -d c=cosine f=$(head -n1 tmp/cross.csv | cut -c 11-) |
q -H -O -d ',' '
select fld1, fld2, cosine
from -
where fld1 != fld2
order by fld1, cosine desc

' | # urlごとに上位5件を取得する
sed -e 's/,/ /g' |
body rank ref=1 |
sed -e 's/fld1 fld2 cosine/rank fld1 fld2 cosine/' |
sed -e 's/ /,/g' |
q -H -O -d ',' '
select fld1,fld2 from -
where rank <= 5
' |
mtra k=fld1 f=fld2 | # url,類似記事一覧 というデータの持ち方をする
sed -e 's/fld1%0/fld1/' |
csvjson  # パースしやすいようにjson形式にする

結果

ハードディスクの寿命分布を比較 という記事に対しての結果を見てみます。

アクセスログを使った場合(メモリベース, コサイン類似度)

コンテンツベース, BoWでコサイン類似度

となりました。

さいごに

各記事の下に表示出来たら楽しいなあ。

トップページからページングして2ページ目へのアクセスはあるのか

サイトの顔になるトップページですが、過去の記事へと遡ってくれる人は、どれほどいるのでしょうか。

蓄積しているログを集計しました。 集計期間は、2015/9/14から2016/1/18です。

トップページのPV数482のうち、2ページ目への遷移は16でした。 また、遷移した合計数は123でした。

意外にもサイト内を巡回してくれるのですね。

遷移先を上位から

URL PV数
http://blog.gepuro.net/page/2 16
http://blog.gepuro.net/archives/118 9
http://blog.gepuro.net/category/統計/ 8
http://blog.gepuro.net/archives/131 7
http://blog.gepuro.net/archives/129 6
http://blog.gepuro.net/archives/133 6
http://blog.gepuro.net/archives/136 6
http://blog.gepuro.net/archives/134 5
http://blog.gepuro.net/archives/135 5
http://blog.gepuro.net/archives/119 4

となっていました。

集計期間から推測すると、右側にある「最近の投稿一覧」もクリックされているようです。

他の記事へアクセスして貰いやすくするためには、自動ロードも必要なのかもしれないです。 http://www.infinite-scroll.com/infinite-scroll-jquery-plugin/ こういうのも使ってみたいなあ。

集計用のコマンド

cat data/* |
jq -r '
select(.url == "http://blog.gepuro.net/") |
[.sid, .date, .url] | @csv' |
q -H -O -d ',' '
select
count(1) as pv_toppage
from -
'

echo

cat data/* |
jq -r '
select(.site=="blog.gepuro.net") |
select(.referrer == "http://blog.gepuro.net/") |
select(.url != "http://blog.gepuro.net/") |
[.sid, .url] | @csv' |
awk 'BEGIN{FS=","}{if($2~/http:\/\/blog\.gepuro\.net\/page/){cnt+=1}}END{print NR","cnt","cnt/NR}' |
header -a '"トップページからの遷移数","ページング数", "割合"'

echo

cat data/* |
jq -r '
select(.site=="blog.gepuro.net") |
select(.referrer == "http://blog.gepuro.net/") |
select(.url != "http://blog.gepuro.net/") |
[.sid, .url] | @csv' |
header -a '"sid","url"' |
q -H -O -d ',' '
select
url,
count(1) as pv_url
from -
group by url
order by 2 desc
limit 10
'

jqコマンド便利だなあ。

サイトのロゴ画像はクリックされるのか

一般的なwebサイトでは、タイトルのロゴ画像にトップページへのリンクを張っています。とは言え、これをクリックしてくれる人って、一体何人いるのだろうかと疑問に感じたので、調べてみました。

計測方法

ロゴ画像のリンクを「http://blog.gepuro.net/?from=logo」として、webビーコンを確認する。

JSONで保存しているので、jqコマンドやqコマンドを下のように使う。

cat data/* |
jq -r '
select(.site=="blog.gepuro.net") |
select(.unixtime >= 1450334421 and .unixtime <= 1452177993) |
[.sid, .url] | @csv' |
header -a '"sid","url"' |
q -H -O -d ',' '
select sid,
max(
case 
when url = "http://blog.gepuro.net/?from=logo" then 1 else 0
end
) as logo_flag
from -
group by sid
' |
q -H -O -d ',' '
select
count(1) as session_cnt,
sum(logo_flag) as click_logo_cnt,
1.0 * sum(logo_flag) / count(1) as click_logo_rate
from -
'

結果は、

session数 ロゴのクリック人数 ロゴのクリック率
635 10 0.016

となった。

ほぼ誰もクリックしないのかよ・・・。orz

twitter上の反応

ABテスト#1 - (似ている|似てない)記事一覧の表示数を変える

ABテストを実装&実施してみる で試してみたABテストの結果です。

個別の記事を開いた時、下部に表示させていた"似ている記事"、"似ていない記事"の表示数を変えてみました。

  • パターンA: それぞれ5件
  • パターンB: それぞれ3件

結果は、

パターン pv数 session数 pv数/session数
A 333 230 1.45
B 297 226 1.31

ウィルコクソンの順位和検定を使いました。

Asymptotic Wilcoxon rank sum test
data:  df[df$ab == 0, ]$pv and df[df$ab == 1, ]$pv
W = 25835, p-value = 0.02378
alternative hypothesis: true mu is not equal to 0

パターンAとパターンBのpv数は、有意水準5%で中央値が等しいという帰無仮説を棄却する。

パターンAの勝ちですね。

2015年を振り返る

今年も早いもので2015年が終わろうとしています。一年を振り返りたいと思います。

ブログのアクセス数から振り返る

セッション数は11856で、ビュー数は17032でした。去年のアクセスに比べれば多くなりましたが、今年は伸びが無かったです。来年は記事のクオリティを上げたり、頻度を上げるように努力せねば。

最もビューが多かった記事は、ハードディスクの寿命分布を比較 で、この記事が元になってバイトに結びついたのは嬉しかったです。大学時代の専攻は信頼性工学でしたので、仕事に直接活かすことが出来たのは良い経験でした。

所属組織から振り返る

2015年3月に電気通信大学大学院を卒業しました。先生方や研究室の仲間達には大変お世話になりました。今でも時々交流があるので嬉しいです。

2015年4月からは、新天地で会社員になりました。学生時代に磨いてきたスキルを活かせています。最初の一ヶ月はOJT形式で先輩社員の下で教わり、それ以降は先輩と共に仕事をしたり、1人で案件を担当することもありました。1人で担当した案件によって、局内表彰を頂くことが出来ました!

また、社外向けの勉強会で発表したり、来年の1月は新卒向けのイベントに登壇したりと対外的な活動も出来るようになりました。

10月には始めての海外出張をさせて頂いたりと刺激的な会社員生活をしています。

コミュニティから振り返る

去年に引き続き、今年もJapan.Rを主催させて頂きました。二回目という事もあり、"去年に比べて"スムーズに運営できたと思います。

また、知人の紹介によってR言語コミュニティ以外にも交友関係を築き始めているので、こちらは継続して頑張りたい。

エンジニアとして振り返る

個人で作っている(途中の)ものとして、いくつかあるので記しておきます。

どれも開発段階ですが、多くの学びがあって楽しいです。

他には、scala及びSparkの勉強を始めたり、dockerを使うようになったり、テストコードを意識的に書くようになったり、AWSを使ったりとエンジニアとしての成長を続けています。

来年は何を作ろうかな。

趣味から振り返る

3月に台湾に行きました。旅の記録をブログに書いていなかったので、その時のtwilogを張っておきます。

twilogのリンクを貼っていたら、縦に長くなってしまいました。約2週間かけて、台湾の一周(1040km)を完走しました。学生最後の最高に楽しい思い出です。

また、自転車で箱根越えもしました。距離で言えば長くはないですが、台湾の時の坂と同じからそれ以上の勾配を登り切った時の感動は良いものです。来年も走りたい場所があるので、自転車は続けます。

台湾一周を終えて、次の目標としてランニングを始めました。やるからにはフルマラソン完走が目標です。最初は4kmほどで倒れるほど疲れましたが、現在は22kmほど走れるようになるまで成長しました。俺史上で一番体力がある気がする。3月のフルマラソンに向けて、絶賛トレーニング中。。。

あと、splatoonのためにWiiUを買いました。腕前は低めorz

まとめ&2016年に向けて

今年は新しい事ばかりの楽しい一年でした。来年もこの調子で多くの事を経験したいです。大学時代の住み慣れた町を出て、新しい場所へ引っ越します。来年やりたい事が盛り沢山なので、良い時間を過ごせそうです。仕事に関しては、守りに入らないように攻めの姿勢で取り組んでいく所存です。周りの方々には迷惑をかけると思いますが、今後ともよろしくお願いします。