【R / Python】UMAP でアクセスログを可視化してみた

今回は UMAP で当ブログのアクセスログ (Nginx) を可視化してみます。実行環境は macOS 10.13, CPU 1.6GHz Intel Core i5, メモリ 8GB です。

UMAP

次元削減のアルゴリズムは大きく2つのカテゴリに分類される。

  • データ内の距離構造を保持する手法 (e.g. PCA, MDS adn Sammon mapping)
  • 大域的な距離の上で局所的な距離を保持する手法 (e.g. t-SNE, Isomap, LargeVis, Laplacian eigenmaps, diusion maps, NeRV and JSE)

UMAP (Uniform Manifold Approximation and Projection) は多様体学習による可視化 & 次元削減手法で, t-SNE と同程度の可視化の質を持ちつつも高速に動作する。UMAP は t-SNE に似た結果を示すが, Laplacian eigenmaps における数学的な土台に基づいている。

アルゴリズムの詳細は arXiv の論文を参照のこと。

論文内の実験では MNIST や Fashion MNIST に対して, t-SNE よりも大域的な構造を保持しながら次元削減できていることが示されている。

また, UMAP の実装は GitHub で公開されている。 umap() で指定できる主なパラメータは以下。

  • n_neighbors: 多様体構造の局所近似で使用される近傍点の数。値を大きくすると局所構造が失われても大域的な構造を保持する。小さくすると局所構造を保持する。 2 ~ 100 の範囲で設定。 (デフォルト値は15)
  • n_components: 埋め込む空間の次元数。可視化目的の場合は通常 2 ~ 3 に設定。 (デフォルト値は2)
  • min_dist: どれくらい一緒に埋め込むかを制御する距離。大きな値を指定するとより均等に分散し, 小さい値を指定すると近い点をまとめて埋め込み多様体上でより近くに引き寄せる。0.001 ~ 0.5の範囲で設定。 (デフォルト値は0.1)
  • metric: 高次元空間のおける距離を測る metrics で UDF (ユーザ定義関数) も使える。 (e.g. euclidean, manhattan, mahalanobis, cosine etc)
  • local_connectivity: 必要とされる局所接続性。値が大きいほど多く接続される。 (デフォルト値は1)

今回はアクセスログを UMAP で可視化してみた。

アクセスログ

今回使うデータは当ブロブのアクセスログで, Nginx のログフォーマットは以下。

$ less /etc/nginx/nginx.conf
...
            log_format ltsv 
                      'domain:$host\t'
                      'host:$remote_addr\t'
                      'user:$remote_user\t'
                      'time:$time_local\t'
                      'method:$request_method\t'
                      'path:$request_uri\t'
                      'protocol:$server_protocol\t'
                      'status:$status\t'
                      'size:$body_bytes_sent\t'
                      'referer:$http_referer\t'
                      'agent:$http_user_agent\t'
                      'response_time:$request_time\t'
                      'cookie:$http_cookie\t'
                      'set_cookie:$sent_http_set_cookie\t'
                      'upstream_addr:$upstream_addr\t'
                      'upstream_cache_status:$upstream_cache_status\t'
                      'upstream_response_time:$upstream_response_time';
...

今回の実行環境でも動作できるように, 生ログ全体の 1% を非復元抽出でランダムサンプリングした上で, 前処理・特徴選択を行って 62269 x 614 のサイズとなった。以下の特徴量を持つ。

  • HTTP Method: カテゴリカル変数 (e.g. GET, POST, HEAD)
  • HTTP Status Code: カテゴリカル変数 (e.g. 200, 404, 500)
  • Response Size: 連続変数
  • Response Time: 連続変数
  • Upstream Response Time: 連続変数
  • Domain: カテゴリカル変数 (ドメイン名, IPアドレス)
  • Referer: カテゴリカル変数 (ドメイン名, IPアドレス)
  • User Agent Family: カテゴリカル変数 (e.g. Android, Windows など推定された User Agent Group)

ちなみに UA のパースは {uaparserjs} パッケージで行った。

UMAPの結果

UMAP の実行は Python で行った。n_neighbors=30 としそれ以外のパラメータはデフォルト値で実行した結果が以下。
共分散が 0 の裾の長い二変量正規分布のような分布となった。

上記の結果から以下のように解釈をしてみた。

  • GET メソッドかつ status code が 2xx/3xx 台の点は中央付近に集まっていることから, これらは通常のブログ閲覧のリクエストの可能性
  • reponse_time と response_size はおそらく相関があり中心付近に集まっていることから, これらは画像取得のリクエストの可能性
  • POST メソッドかつ UA が Mac OS X のものが中心から遠い点に含まれていることから, これらはブログ投稿のリクエストの可能性

以下は, 上から n_neighbors を 15, 30, 100 とした時の埋め込み結果。 n_neighbors の値が大きくなるに伴い大域的な構造の保持を優先しようとしていることがわかる。

上記に対応する UMAP の実行時間が以下。 実行時間は n_neighbors の影響を受けやすいことがわかる。

dataset size n_neighbors time
62269 x 614 15 (default) 371.92 s
62269 x 614 30 4313.71 s
62269 x 614 100 25544.18 s

おわりに

t-SNE との比較もしたかったのですが手元のPCではリソース不足のため断念しました。


[1] access_log – Nginx.org
[2] 【多様体学習】LLEとちょっとT-SNE