Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Elasticsearchで教師あり機械学習 ~Data Frame Analyticsまとめ ~

この記事は Elastic stack (Elasticsearch) Advent Calendar 2020 の 22日目の記事になります。

こんにちは、@shin0higuchiです😊
今回のテーマは、「Elasticsearchで教師あり機械学習」ということで、Data Frame Analyticsの機能を紹介します。

はじめに

2017年ごろから、Elasticsearchには時系列データの異常検知に特化した、教師なしの機械学習機能がありました。
詳しくは、昨年(2019年)のAdvent Calendarで @yukata_uno さんが
「Elastic Machine Learningの歴史を振り返る」という記事を書いていらっしゃるのでそちらをご参照ください。

今年は Elasticsearch の Ver.7.2 で transform が利用可能となり、
そこから outlier detection, regression, classification などの機能が続々リリースされました。

本記事では、その中の Classification を利用して、実際に機械学習を試してみます。
※X-Packの有償機能を利用するため、トライアルライセンスを有効化しています。

利用するデータについて

データセットとしてはこちらを利用します。
www.kaggle.com

データに含まれる「年齢」、「給与」、「婚姻状況」、「クレジットカードの制限」、「クレジットカードのカテゴリ」などの情報から、その顧客がクレジットカードを解約するかどうかを予測することを今回の目標としましょう。
Attrition_Flagのカラムに「Existing Customer」「Attrited Customer」の2値が入っており、これを予測する形になります。

では、CSVファイルをKibanaから取り込んで中身を見てみましょう。
f:id:acro-engineer:20201221035300p:plain:w800

bank_churners という名前のインデックスに取り込みます。
f:id:acro-engineer:20201221035313p:plain:w800

機械学習ジョブの作成

Machine LearningのメニューからData Analytics Jobの「Create job」を選択し...
f:id:acro-engineer:20201221035738p:plain:w800

Classificationを選択します。
Dependent variable には分類の対象となる Attrition_Flag を指定します。
f:id:acro-engineer:20201221040055p:plain:w800

分析に利用するフィールドはチェックボックスで選択することができます。
ここでは前述の「年齢」、「給与」、「婚姻状況」、「クレジットカードの制限」、「クレジットカードのカテゴリ」を含む14フィールドを指定しています。
f:id:acro-engineer:20201221205009p:plain:w800


ElasticsearchのClassificationでは、内部的にBoosted Decision Tree Regressionによる学習をおこないます。
設定項目は分析の種類によって異なりますが、Classificationの場合は以下の通りです。

項目名 説明
Feature importance values 結果に大きく影響したカラムを取得する最大数
Prediction field name 予測結果を入れるフィールド名
Top classes 分類するクラス数
Model memory limit モデルの上限メモリ量
Maximum number of threads 分析時の最大スレッド数

ハイパーパラメータについては、内部で適切な値を選択してくれるようなので、詳しくない場合はデフォルトで良いでしょう。ここでは説明を割愛します。Hyperparameter optimization | Machine Learning in the Elastic Stack [7.10] | Elastic

設定の詳細はこちらをご参照ください。
Concepts | Machine Learning in the Elastic Stack [7.10] | ElasticClassification | Machine Learning in the Elastic Stack [7.10] | Elastic

学習の開始

ジョブ名などを決めたあと、学習を開始することができます。
f:id:acro-engineer:20201221215752p:plain:w800

学習の進行度はプログレスバーで確認することができます。
学習が終わると「View Results」のリンクが表示されます。
f:id:acro-engineer:20201221220338p:plain:w800

結果の確認

結果画面では、影響度の大きいカラム・モデルの評価値・推定結果などが確認可能です。
まずは、影響度の大きいカラムのランキングを見てみましょう。
Avg_Utilization_Ratio, Months_Inactive_12_mon, Contacts_Count_12_monなどが上位となっています。カードの利用率や過去12カ月の動きが少ない人が解約しやすいと思われるので、感覚的には正しそうな印象ですね。
※表示されるフィールド数は、先ほど設定したFeature importance valuesによります。
f:id:acro-engineer:20201221220653p:plain:w800

そしてこちらが、モデルの評価値です。
実際の推論結果と、正解ラベルをマトリクスで示したものになります。
解約しそうなユーザーを漏れなく見つけることが重要になるので、Attrited Customer側の正解率をもっと上げたい(再現率を上げたい)印象はあるものの、解約と継続の正解率はそれぞれ68%, 80%となっており、総じて悪くない結果なのではないでしょうか。
f:id:acro-engineer:20201221225645p:plain

作成したモデルの利用

学習したモデルは、inferenceという機能を通じて、ingest nodeの processor や、Aggregation から利用することができます。

たとえば次のような ingest processor を用意することで、新規に取り込むドキュメントを学習済みのモデルで分類することができます。

{
  "inference": {
    "model_id": "bank_churners-1608484239997",
    "target_field": "BunkChrner_prediction_infer",
    "inference_config": {
      "classification":{
        "num_top_classes": 2,
        "results_field": "prediction",
        "top_class_results_field": "probabilities"
      }
    }
  }
}

※ model_idは GET /_ml/trained_models で確認することができます

Aggregationについては割愛します。親となるAggregationの値に対して推論をかける形になりますので、 inference processorと基本的な設定内容は同じです。詳しくはこちらを参照してください。Inference bucket aggregation | Elasticsearch Reference [master] | Elastic

まとめ

以上のように、Elasticsearchの機械学習が大幅に強化され、専門的な知識がなくとも利用できるようになって来ています。Elasticsearchの強みは、集約したデータを様々なユースケースに横断的に活用できる点だと思っていますが、今回紹介した機能はさらにその幅を広げてくれるのではないかと思っています。
皆さんも是非ElasticsearchのData Frame Analyticsを試してみてください。

それでは。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

【データ分析】
Kaggle Masterと働きたい尖ったエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com

Azure Container Instancesを使ってAngular+FastAPIなWebアプリを動かしてみた

こんにちは。
初登場になります、普段はフロントエンドの開発をしている長野です。

本記事は、Azure Advent Calendar 2020の19日目の記事になります。

最近、Angular+FastAPIでアプリを実装したのですが、
そのアプリを、仮想マシンではなく、サーバレス・コンテナを使ったWebアプリとして構築してみたくなりました。

調べている中で、Azure Container Instances(以下、ACI)を見つけ、実際に試してみたところ、
少し工夫が必要だったものの、全体としてはとても簡単に動かすことができました。

この記事では、私が実際にACIを使ってみた中で学んだ、ACI上でのWebアプリの動かし方について、
特にフロントエンドとバックエンドの連携の際に注意するべき点を中心に、まとめました。

この記事を見ながら、皆さんも実際にこの手軽さを体験していただきたいと思います。

それでは、さっそく始めましょう!

始めに: "Azure Container Instances"とは

ACIとは、高速かつ簡単にコンテナを実行できるサービスです。メリットは次の2つです。

  1. コンテナイメージさえあればコマンド一つで立ち上げることができ、さらにインターネット上に直接公開をすることもできるので、すぐに利用することができます。
  2. コンテナの起動時間とその間に使用したCPUとメモリに応じて課金されるので、使わないときは停止する等をすれば、コストも抑えることができます。

Webアプリの説明

まずはじめに、例として扱うWebアプリを説明します。

今回、本の情報を管理する、簡単な図書管理システムを構築してみました。
一覧画面と詳細画面があり、本の情報の、登録・確認・編集・削除ができるようになっています。

f:id:acro-engineer:20201219101050p:plain
図書管理システムの一覧画面
f:id:acro-engineer:20201219101458p:plain
図書管理システムの詳細・編集画面

フロントエンドはnginx + Angular、バックエンドはFastAPIを、今回は使用しました。

また、今回開発するにあたり、以下の環境を使用しました。

  • Node.js: v14.15.0
  • Docker: 20.10.0
  • Azure CLI: 2.16.0

なお、ディレクトリ構成は以下のようになっています。

sample-lsm-app     
├─deploy-aci.yaml
├─docker-compose.yml
├─client
│  ├─angular.json
│  ├─Dockerfile
│  ├─package-lock.json
│  ├─package.json
│  ├─dist
│  │  └─client
│  │      └─フロントエンドの本体コードのビルド媒体
│  ├─nginx
│  │  └─default.conf
│  ├─node_modules
│  └─src
│      ├─app
│      │  └─フロントエンドの本体コード
│      └─environments
│         └─フロントエンドの環境設定
└─server
    ├─Dockerfile
    ├─poetry.lock
    ├─pyproject.toml
    ├─.venv
    ├─src
    │  └─バックエンドの本体コード
    └─tests
       └─バックエンドのテストコード

Webアプリの構成

本アプリのAzure上での構成は以下のようになっています。

f:id:acro-engineer:20201219100505p:plain
サンプルアプリ構成イメージ

フロントエンド、バックエンド、それぞれ別でコンテナイメージを作成して、Azure Container Registry(以下、ACR)に登録、
それらをACI上にデプロイすることで、Webアプリを動かしています。

それでは実際に、ローカルで作成したWebアプリをAzure上に構築していきましょう。

WebアプリのAzure化

1. フロントエンドとバックエンドの結合部分の準備

まずは、フロントエンドからバックエンドへAPIを呼んで、データのやりとりができるように準備をします。
今回、Angularで作成したフロントエンドをnginxサーバー上で動かすので、以下のようなdefault.confを用意しました。

server {
    listen  80;
    server_name localhost;

    root    /usr/share/nginx/html;
    index   index.html index.htm;

    location /api/ {
        proxy_pass http://localhost:8000;
    }
}

ここでのポイントは、

proxy_pass http://localhost:8000;

の部分です。通常、Docker上で動かす際は、"localhost"の部分にコンテナ名を指定しますが、
Azure Container Instances上で動かす際には、"localhost"での指定になります。

以上の設定で、"http://<ACIの公開FQDN>:80/api/~" とURLを指定することで、フロントエンドからAPIを呼ぶことができるようになりました。

ここから先はAngularの内容です。

nginxで設定したAPIのURLを呼べるように、environment.prod.tsに以下のように記述します。

export const environment = {
  production: true,
  server: 'http://<ACIの公開FQDN>'
};

そして、実際にAPIを呼ぶservice.tsでは、以下のようにURLを指定するようにします。

  fetchBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(environment.server + '/api/books');
  }

最後に、以下のコマンドで本番環境としてビルドを行うことで、フロントエンドをACI上で動かすための準備が完了しました。

npm build --prod

2. コンテナイメージ化

Dockerを使用し、フロントエンド側・バックエンド側両方のコンテナイメージを作成します。

まず、それぞれのDockerfileの内容はこちらです。

フロントエンド側

FROM nginx:1.16
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

# distディレクトリ配下に作成されたビルド媒体をコピー
COPY ./dist/client /usr/share/nginx/html

バックエンド側

FROM python:3.8-slim
WORKDIR /usr/src/app
RUN pip install poetry

# ソースコード類のコピー
COPY . .

# 実行環境の用意
RUN poetry export -f requirements.txt > requirements.txt
RUN pip install -r requirements.txt

# FastAPIの起動
CMD python /src/main.py

これらをまとめて立ち上げるための、docker-compose.yamlはこちらです。

version: "3"
services:
  client:
    build: ./client
    image: sample-lms-app_client:1.0
    container_name: sample-lms-app_client
    ports:
      - 80:80
  server:
    build: ./server
    image: sample-lms-app_server:1.0
    container_name: sample-lms-app_server
    ports:
      - 8000:8000

docker-composeのbuildコマンドで、コンテナイメージを作成します。

docker-compose build

3. Azure Container Registryの作成

2で作成したコンテナイメージを、登録するためのACRを用意します。

Azure portalから「リソースの作成」を行い、"Container Registry"を検索します。

f:id:acro-engineer:20201219101830p:plain
"Container Registry"で検索すると、作成画面が開きます

必要事項を入力し、ACRを作成します。

f:id:acro-engineer:20201219102111p:plain
ACRの作成画面

作成完了後、「アクセスキー」から、「管理者ユーザー」の設定を有効化します。
こうすることで、ACRに登録したコンテナイメージを利用する際には、パスワードが必要になります。「ログインサーバー」、「ユーザー名」、「password」を、それぞれ手元に控えておきましょう。

f:id:acro-engineer:20201219102414p:plain
ACRへのアクセスキーの表示

4. Azure Container Registryへコンテナイメージの登録

2で作成したコンテナイメージを、3で作成したACRに登録します。

最初に、コマンドプロンプト経由で、ACRにログインを行います。3の最後で取得した、アクセスキーを使用します。

docker login <ログインサーバー> -u <ユーザー名> -p <パスワード>

次に、2で作成したコンテナイメージに、タグ付けを行います。

docker tag sample-lms-app_client:1.0 <ログインサーバー>/sample-lms-app_client:1.0

docker tag sample-lms-app_server:1.0 <ログインサーバー>/sample-lms-app_server:1.0

最後に、pushを行うことで、ACRにコンテナイメージが登録されます。

docker push <ログインサーバー>/sample-lms-app_client:1.0

docker push <ログインサーバー>/sample-lms-app_server:1.0

登録が成功していることは、Azure portalの画面から、「リポジトリ」の画面を開くことで確認ができます。
作成したコンテナイメージが表示されたら、成功です。

f:id:acro-engineer:20201219102625p:plain
登録済みコンテナイメージの一覧

5. Azure Container Instancesの立ち上げ

いよいよ、ACI上にアプリを立ち上げます。

そのために、設定ファイルの作成が必要です。以下のような"deploy-aci.yaml"を作成しました。

apiVersion: 2018-10-01
name: SampleLMSAppACG
# ACIのリソース名
location: japaneast
# リージョン
properties:
  containers:
  # フロントエンド側、バックエンド側それぞれのコンテナの設定
  - name: client
    properties:
      image: <ログインサーバー>/sample-lms-app_client:1.0
      # 使用する、ACR上のコンテナイメージ
      resources:
        requests:
          cpu: 1
          # コンテナを動かすために必要なCPU数(最小1)
          memoryInGb: 1
          # コンテナを動かすために必要なメモリ数(最小1)
      ports:
      - port: 80
        # 使用ポート
        protocol: TCP
        # プロトコル
  - name: server
    properties:
      image: <ログインサーバー>/sample-lms-app_server:1.0
      resources:
        requests:
          cpu: 1
          memoryInGb: 1
      ports:
      - port: 8000
        protocol: TCP
  osType: Linux
  # コンテナのOSタイプ
  ipAddress:
  # IPアドレスの設定
    type: Public
    # パブリックIPか、プライベートIPか
    ports:
    # 公開ポートの設定
    - protocol: tcp
      port: 80
    - protocol: tcp
      port: 8000
    dnsNameLabel: sample-lsm-app
    # DNSラベル設定
  imageRegistryCredentials:
  # ACRのログイン情報
  - server: <SERVER>
    # ログインサーバー
    username: <USERNAME>
    # ユーザー名
    password: <PASSWORD>
    # password

以下、詳細な説明です。

  • type: Public
    • 今回は、ACIを直接公開するため、"Public"を選択しました。
    • 例えば、IP制限等を行うために、VNet下にアプリを立ち上げる場合には、ここで"Private"を選択します。
  • dnsNameLabel: sample-lsm-app
    • 1で設定した"ACIの公開FQDN"は、ここで設定したものです。
    • ここで設定したDNSラベルと、選択したリージョンによって、"<DNSラベル>.<リージョン>.azurecontainer.io"のように、公開FQDNが決定します。

ここで作成した設定ファイルを使って、ACIのデプロイを行います。
デプロイには、AzureCLIをインストールし、コマンドプロンプト上で、"az"コマンドが使えるようになっている必要があります。(Azure CLIのインストール

まずは、Azure CLI上で、ログインが必要です。

az login

これで、アプリをデプロイするための準備がすべて整いました。最後に、以下のコマンドでアプリを立ち上げます。

az container create --resource-group <リソースグループ> --file deploy-aci.yaml

6. 動作確認

作成コマンドを実行したら、Azure portal上で、実際にACIが作成されたか確認してみましょう。
設定ファイルの中で指定したリソース名で、リソースが存在し、かつ「実行中」であることを確認します。

f:id:acro-engineer:20201219230728p:plain
作成したACIの概要画面

ACIが実際に作成できたことが確認できたら、画面に表示されているFQDNから、アプリにアクセスしてみましょう。

f:id:acro-engineer:20201219103300p:plain
ACI上で動いているアプリにアクセスした様子

無事に画面を開くことができました!

もしもここで画面が開けなかった場合は、コンテナのログを見て、エラーが出ていないか確認します。
コンテナのログは、「コンテナー」でコンテナ一覧を開き、見たいコンテナを選択した状態で、「ログ」タブを選ぶことで表示することができます。

f:id:acro-engineer:20201219103504p:plain
コンテナログの表示

詰まりやすいのは、フロントエンドとバックエンドとの連携の部分なので、
コンテナがそれぞれ起動しているのを確認したうえで、この記事の中で紹介している、"environment.prod.ts"や"default.conf"の設定を確認してみてください。

なお、ACIのリソースは一度削除してから、作成コマンドを実行してください。
ACRに登録したコンテナイメージは、登録済みのタグにもう一度pushすることで上書きされます。
しかし一方で、ACIで同じタグのコンテナイメージを利用して再作成する際は、キャッシュが効いてしまうためか、
更新されたコンテナイメージでうまく立ち上げられないようです。

最後に

いかがだったでしょうか?

私が初めてこの構成でアプリを立ち上げようとした時は、
公式のチュートリアルを参考にしたものの、1つのコンテナの場合の説明のみだったため、
フロントエンドとバックエンドとのコンテナ間の連携がなかなかうまくいかず、苦労しました。

ぜひ皆さんも自作のアプリをACI上にデプロイして、動かしてみてください!

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

モノリシックなアプリケーションをマイクロサービス化したいエンジニア募集! - Acroquest Technology株式会社のWebエンジニアの求人 - Wantedlywww.wantedly.com

Karateに性能試験とUI試験を任せてみる

皆さんこんにちは、QA部門のカヤ(@kareyama_r)です。

この記事はソフトウェアテスト Advent Calendar 2020の16日目です。

つい先日、ソフトウェアテスト自動化カンファレンス2020が開催され、テスト自動化に関する様々な取り組みやフレームワークが紹介されていましたね!

私のイチオシのフレームワークは、去年・一昨年と同カンファレンスで取り上げられたKarateですが、今年は話題に上がっていなかったので取り上げたいと思います。

最初はAPI試験用のフレームワークとして発表されたKarateですが、最近は負荷試験やUI試験自動化までカバーするようになっています。

今回はこれらの機能を一気に試してみよう!ということで、アプリに負荷をかけながら、画面のロード時間を計測するという内容でKarateを使ってみました。

対象アプリとしてElastic Stackを使用します。

DBとなるElasticsearch・BIツールのKibanaを立ち上げて、Elasticsearchに負荷をかけつつKibanaへのレスポンスの影響を確認します。

以下が構成イメージです。 f:id:acro-engineer:20201211015421p:plain

続きを読む

特徴量エンジニアリングのライブラリ xfeat を使ってみて便利だったこと

こんにちは。機械学習エンジニアをしている古賀です。
最近は愉快な上司@tereka114 のもと、精度の上がらないモデルに四苦八苦しています。

そんな私が普段データ分析をする際に難しいことの一つとして、特徴量エンジニアリングがあります。
特徴量エンジニアリングとは、元のデータに新たな特徴量を追加することでモデルの精度を向上させるプロセスのことです。
この結果によってモデルの精度が大きく変わりますが、正しく実行するにはデータへの深い理解やデータ分析力が必要になります。

私もあまり得意ではないのですが、これを簡単にする xfeat という便利なライブラリがあると上司が教えてくれたので、実際に使ってみて便利だったことをまとめました。

※本記事は、Pythonその3 Advent Calendar 2020 の15日目の内容になります。

目次は以下です。

xfeat とは

PFNさんが公開している特徴量エンジニアリングと特徴量探索のためのライブラリです。
一般的にデータを分析するときには、データの型を調べたり、どんな特徴量が精度に効いているか実験したりと、骨が折れますよね。
xfeat を使うと、このようなデータ分析や機械学習の際のコードをより簡単に書くことができます。
github.com

準備

xfeat を実際に使うための環境の構築と、データの準備を行います。

実行環境

Google Colaboratory を使用しました。

xfeatライブラリのインストール

pip コマンドで簡単にインストールできます。

!pip install git+https://github.com/pfnet-research/xfeat.git

ライブラリのインポート

今回使用するライブラリをあらかじめインポートしておきます。

from sklearn.model_selection import KFold
from functools import partial

import optuna
from xfeat import SelectCategorical, LabelEncoder, Pipeline, ConcatCombination, SelectNumerical, \
    ArithmeticCombinations, TargetEncoder, aggregation, GBDTFeatureSelector, GBDTFeatureExplorer

データセット

今回はみなさんおなじみ、Kaggleで初心者向けに公開されているタイタニックデータを使用しました。

データのダウンロード

下の記事を参考にして "Kaggle APIをインストール" ~ "データのダウンロード" まで実施することで、Google Colaboratory上でタイタニックデータをダウンロードできます。
qiita.com

データの読み込み

train_df = pd.read_csv("/content/train.csv")
test_df = pd.read_csv("/content/test.csv")

データの概要

1912年4月15日、氷山に衝突して沈没したタイタニック号の、乗組員の生存状況データです。
全部で1309件あり、12個の説明変数と、1個の目的変数(Survived:生存状況)からなります。
データの説明は、Kaggleのタイタニックデータページに載っています。 www.kaggle.com

中身を確認すると、下のようになっています。

train_df.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.250000 None S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.283302 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.925000 None S

xfeat を使ってみて便利だったこと

1. カテゴリカルデータ、数値データを中身を調べずに抽出可能

まずはデータセットから、カテゴリカルデータ、数値データをそれぞれ抽出してみます。
通常であれば、データの中身を確認し、型を調べてからでないと特定の型のデータだけを抽出することはできません。
しかし xfeat を使うとその手間が省け、データの中身を調べずに自動で抽出できます。

Ⅰ. カテゴリカルデータのみを抽出する

SelectCategorical().fit_transform(train_df).head(3)
Name Sex Ticket Cabin Embarked
0 Braund, Mr. Owen Harris male A/5 21171 None S
1 Cumings, Mrs. John Bradley (Florence Briggs Th... female PC 17599 C85 C
2 Heikkinen, Miss. Laina female STON/O2. 3101282 None S |

Ⅱ. 数値データのみを抽出する

SelectNumerical().fit_transform(train_df).head(3)
PassengerId Survived Pclass Age SibSp Parch Fare
0 1 0 3 22.0 1 0 7.250000
1 2 1 1 38.0 1 0 71.283302
2 3 1 3 26.0 0 0 7.925000

データの中身を調べず、たった一行で抽出できました。

2. Label Encoding が簡単にできる

特定のカテゴリカルデータに対し、Label Encodingを行う

Label Encoding とは、カテゴリカルデータの各カテゴリを整数に置き換える変換です。
ここでは、学習データからカテゴリカルデータを抽出し、それらに Label Encoding を行っています。

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Name", "Ticket"]),
    LabelEncoder(output_suffix=""),
])

encoded_df = encoder.fit_transform(train_df)
encoded_df.head(3)
Sex Cabin Embarked
0 0 -1 0
1 1 0 1
2 1 -1 0

3. Target Encoding が簡単にできる

Target Encodingとは

目的変数を用いてカテゴリカルデータを数値に変換する方法のことです。
非常に有効な特徴量となる場合もありますが、目的変数を Leak させてしまう可能性もあるので、十分注意する必要があります。
※Leak とは、学習の過程では本来得られない目的変数の情報が見えてしまい、カンニング状態になってしまうことで、過学習してしまうことです。
リークを防ぐため自身のレコードの目的変数を使わないように変換をする必要があるため、少し実装が面倒なのですが、 xfeat を使うと下のようにシンプルに書くことができます。

fold = KFold(n_splits=5, shuffle=False)
encoder = TargetEncoder(
    input_cols=["Cabin"], 
    target_col="Survived",
    fold=fold,
    output_suffix="_re"
    )

encoded_df = encoder.fit_transform(train_df)
encoded_df[["Survived", "Cabin", "Cabin_re"]].head(3)
Survived Cabin Cabin_re
0 0 None 0.303867
1 1 C85 0.303867
2 1 None 0.303867

sklearn や Pandas だけを使うと結構長くなってしまう処理が、これだけ短くシンプルにかけるのはうれしいですね。

4. カテゴリカルデータの組み合わせが簡単にできる

Ⅰ. 二つのカテゴリカルデータを組み合わせる

ここでは、"Sex", "Cabin", "Embarked" のカテゴリカルデータのうち、二つを組み合わせた特徴量をそれぞれ作成しています。
もしも Pandas だけを使って作成しようとした場合、["Sex", "Cabin"], ["Cabin", "Embarked"], ["Embarked", "Sex"] の組み合わせごとに処理を書く必要があり、少し面倒です。
xfeat を使えば、特に処理を書かずに対象の変数と条件を指定するだけで生成してくれるので、実装がとても楽でした。

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Ticket", "Name"]),

    # If there are many categorical columns,
    # users can specify the columns to be combined with `input_cols` kwargs.
    # `r=2` specifies the number of columns to combine the columns.
    ConcatCombination(
        # drop_origin=True, 
        output_suffix="_re", 
        r=2),
])

encoded_df = encoder.fit_transform(train_df)
encoded_df.head(3)
Sex Cabin Embarked SexCabin_re SexEmbarked_re CabinEmbarked_re
0 male None S male_NaN_ maleS _NaN_S
1 female C85 C femaleC85 femaleC C85C
2 female None S female_NaN_ femaleS _NaN_S

Ⅱ. 三つのカテゴリカルデータを組み合わせる

ここでは、"Sex", "Cabin", "Embarked" の三つのカテゴリカルデータを組み合わせた特徴量を作成しています。

encoder = Pipeline([
    SelectCategorical(exclude_cols=["Ticket", "Name"]),

    # If there are many categorical columns,
    # users can specify the columns to be combined with `input_cols` kwargs.
    # `r=2` specifies the number of columns to combine the columns.
    ConcatCombination(
        # drop_origin=True, 
        output_suffix="_re", 
        r=3),
])

encoded_df = encoder.fit_transform(train_df)
encoded_df.head(3)
Sex Cabin Embarked SexCabinEmbarked_re
0 male None S male_NaN_S
1 female C85 C femaleC85C
2 female None S female_NaN_S

5. 数値データの加算が簡単にできる

兄弟/配偶者、両親/子供の数を加算した特徴量を作成する

カテゴリカルデータと同様、数値データの組み合わせ(ここでは加算)も簡単に書くことができます。

# 2-order Arithmetic combinations.
encoder = Pipeline(
    [
        SelectNumerical(),
        ArithmeticCombinations(
            # 兄弟/配偶者、両親/子供の数を加算した特徴量を作成する
            input_cols=["SibSp", "Parch"], 
            drop_origin=True, 
            operator="+", 
            r=2,
        ),
    ]
)

encoded_df = encoder.fit_transform(train_df)

元のデータを確認しておきます。

train_df[["SibSp", "Parch"]].head(3)
SibSp Parch
0 1 0
1 1 0
2 0 0

変換後の結果は下になります。

encoded_df.head(3)
SibSpParch_combi
0 1
1 1
2 0

元データを加算した特徴量が作成されていることが確認できました。

6. Aggregation が簡単にできる

性別ごとに、年齢、Pclass の平均、最大を集計した特徴量を作成する

タイトルのような特徴量を作成しようとすると、Pandas を用いると、下のように長くて少々複雑なコードになってしまいます。

from copy import deepcopy
aggregated_df = deepcopy(train_df)
 
# 性別ごとの年齢の平均値を特徴量に追加
sex_mean_df = train_df.groupby('Sex')['Age'].mean()
aggregated_df.loc[aggregated_df['Sex'] == 'female', 'agg_mean_Age_grpby_Sex'] = sex_mean_df['female']
aggregated_df.loc[aggregated_df['Sex'] == 'male', 'agg_mean_Age_grpby_Sex'] = sex_mean_df['male']
 
# 性別ごとの年齢の最大値を特徴量に追加
sex_max_df = train_df.groupby('Sex')['Age'].max()
aggregated_df.loc[aggregated_df['Sex'] == 'female', 'agg_max_Age_grpby_Sex'] = sex_max_df['female']
aggregated_df.loc[aggregated_df['Sex'] == 'male', 'agg_max_Age_grpby_Sex'] = sex_max_df['male']
 
# 性別ごとのPclassの平均値を特徴量に追加
pclass_mean_df = train_df.groupby('Sex')['Pclass'].mean()
aggregated_df.loc[aggregated_df['Pclass'] == 'female', 'agg_mean_Pclass_grpby_Sex'] = pclass_mean_df['female']
aggregated_df.loc[aggregated_df['Pclass'] == 'male', 'agg_mean_Pclass_grpby_Sex'] = pclass_mean_df['male']
 
# 性別ごとのPclassの最大値を特徴量に追加
pclass_max_df = train_df.groupby('Sex')['Pclass'].max()
aggregated_df.loc[aggregated_df['Pclass'] == 'female', 'agg_max_Pclass_grpby_Sex'] = pclass_max_df['female']
aggregated_df.loc[aggregated_df['Pclass'] == 'male', 'agg_max_Pclass_grpby_Sex'] = pclass_max_df['male']

→この処理を、xfeatを使うと下のように非常にシンプルに書くことができます。これは嬉しいですね^^

aggregated_df, aggregated_cols = aggregation(train_df,
                     group_key="Sex",
                     group_values=["Age", "Pclass"],
                     agg_methods=["mean", "max"],
                     )
                     
cols_to_show = ["Sex"] + aggregated_cols
aggregated_df[cols_to_show].head(3)
Sex agg_mean_Age_grpby_Sex agg_mean_Pclass_grpby_Sex agg_max_Age_grpby_Sex agg_max_Pclass_grpby_Sex
0 male 30.726645 2.389948 80.0 3
1 female 27.915709 2.159236 63.0 3
2 female 27.915709 2.159236 63.0 3

7. LightGBM の feature importance を用いた特徴量の選択が簡単にできる

LightGBM の feature importance を見ると、どの特徴量がどれだけモデルの精度に影響を与えたかを知ることができます。
これにより、データの傾向を捉えたり、新たに追加した特徴量の効果があるかなどを調べたりできます。
通常、feature importance を確認するためには、

  1. モデルの作成
  2. 学習
  3. feature importance の抽出

というステップを経る必要があります。これを xfeat を使うと、

  1. 特徴量選択器の作成
  2. feature importance の抽出

と短いステップで書くことができます。

今回は、この記事で紹介した手法を使って元のデータにいくつか特徴量を追加し、特徴量の選択をしてみました。
最終的な入力データは以下です。

encoded_train_df.head(5)

f:id:acro-engineer:20201214230743p:plain

  • "Cabin_target"
    • "Cabin" データに Target Encoding した特徴量(3で紹介)
  • "SexCabin", "SexEmbarked", "CabinEmbarked"
    • "Sex", "Cabin", "Embarked" データのうち、二つを組み合わせた特徴量(4で紹介)
  • "SibSpParch_combi"
    • "SibSp", "Parch" データを加算した特徴量(5で紹介)
  • "agg_mean_Age_grpby_Sex", "agg_mean_Pclass_grpby_Sex", "agg_max_Age_grpby_Sex", "agg_max_Pclass_grpby_Sex"
    • 性別ごとに、年齢、Pclass の平均、最大を集計した特徴量(6で紹介)

この入力データに対して、特徴量の選択をします。

# LightGBMのパラメータを設定する
lgbm_params = {
    "objective": "binary",
    "metric": "binary_error",
}
fit_kwargs = {
    "num_boost_round": 10,
}

# 特徴量選択器を作成する
selector = GBDTFeatureSelector(
    target_col="Survived",
    threshold=0.5,
    lgbm_params=lgbm_params,
    lgbm_fit_kwargs=fit_kwargs,
)

GBDTFeatureSelector() で、特徴量選択をするインスタンスを作成しています。
また threshold の値で、入力データの変数のうちいくつを選択するかの設定をします。
今回は threshold の値が0.5, 元の説明変数が17個だったので、[17 * 0.5] = 8個の変数が最終的に選択されます。

selected_df = selector.fit_transform(encoded_train_df)
print("Selected columns:", selector._selected_cols)

Selected columns: ['Age', 'Fare', 'Cabin_target', 'SexCabin', 'Pclass', 'Sex', 'SibSpParch_combi', 'SexEmbarked']
年齢、運賃、キャビン番号の Target Encoding、性別×キャビン番号、チケットクラス、性別、兄弟/配偶者+両親/子供の数、性別×乗船場、の重要度が高いことがわかりました。

8. optuna と組み合わせて、特徴量探索が簡単にできる

7 ではパラメータを固定の状態で特徴量選択を行いましたが、さらに optuna と組み合わせることで、
Hyper Parameter Tuning によって LightGBM のモデルに重要な特徴量を探索できます。

今回は、特徴量選択数(入力データの変数のうちいくつを選択するか)と LightGBM のパラメータ二つ(num_leaves, max_depth)に幅を持たせて特徴量探索を行ってみました。入力データは7で使ったものと同じです。
※特徴量選択数は GBDTFeatureExplorer()threshold_range=(0.5, 1.0), で設定しています。
 optuna がこの (0.5, 1.0) の範囲で Hyper Parameter Tuning をして最適な値を探し出してくれます。  

LGBM_PARAMS = {
        "objective": "binary",
        "metric": "binary_error",
        "verbosity": -1,
}


def objective(df, selector, trial):
    selector.set_trial(trial)
    selector.fit(df)
    input_cols = selector.get_selected_cols()

    # Hyper Parameter Tuning するパラメータと範囲を設定する
    lgbm_params = {
        'num_leaves': trial.suggest_int("num_leaves", 3, 10),
        'max_depth': trial.suggest_int("max_depth", 3, 10),
    }
    lgbm_params.update(LGBM_PARAMS)

    # Evaluate with selected columns
    train_set = lgb.Dataset(df[input_cols], label=df["Survived"])
    scores = lgb.cv(lgbm_params, train_set, num_boost_round=100, stratified=False, seed=1)
    
    binary_error_score = scores['binary_error-mean'][-1]
    return 1 - binary_error_score


# 特徴量探索のための説明変数を設定する
# encoded_train_df は、No.7で作成したDataFrameと同じ
input_cols = list(encoded_train_df.columns)
input_cols.remove('Survived')


# 特徴量探索器を作成する
selector = GBDTFeatureExplorer(
    input_cols=input_cols,
    target_col="Survived",
    fit_once=True,
    threshold_range=(0.8, 1.0),
    lgbm_params=LGBM_PARAMS,
)

# Hyper Parameter Tuning を行う
study = optuna.create_study(direction="minimize")
study.optimize(partial(objective, encoded_train_df, selector), n_trials=100)

# 選択された特徴量を確認する
selector.from_trial(study.best_trial)
print("Selected columns:", selector.get_selected_cols())

Selected columns: ['Age', 'Fare', 'Cabin_target', 'SexCabin', 'SibSpParch_combi', 'Pclass', 'Cabin', 'SexEmbarked', 'Sex']
年齢、運賃、キャビン番号の Target Encoding、性別×キャビン番号、兄弟/配偶者+両親/子供の数、チケットクラス、キャビン番号、性別×乗船場、性別、の重要度が高いことがわかりました。

最終的なパラメータの値と学習データの正答率も見てみましょう。

print(study.best_params)

{'GBDTFeatureSelector.threshold': 0.5095199015409342, 'num_leaves': 9, 'max_depth': 8}

print(study.best_value)

0.8067415730337079
Cross Validation では、正答率80.7%でした。

※ちなみに、上のパラメータを使って Kaggle の Leaderboard で submit してみたところ、 スコアは 0.77511 でした。
これだけシンプルに書けて、特徴量の探索から予測までできるのは嬉しいですね。

9. cuDF にも適用可能

xfeat は、 cuDF を使って pandas.DataFrame と同様に Target Encoding や Aggregation を行うことができます。
cuDF とCuPyを使うことで、 Pandas だけを使うよりもなんと10~30倍速く処理を実行できるそうです(公式ページより)。
今回は、 cuDF を使って xfeat の Target Encoding を使ってみました。
cuDF のインストール方法や使い方についての詳細は、下の記事を参考にしてください。

acro-engineer.hatenablog.com

fold = KFold(n_splits=5, shuffle=False)
encoder = TargetEncoder(
    input_cols=["Cabin"], 
    target_col="Survived",
    fold=fold,
    output_suffix="_re"
    )

train_cdf = cudf.from_pandas(train_df)  # if cuDF is available.
encoded_train_cdf = encoder.fit_transform(train_cdf)
encoded_train_cdf[["Survived", "Cabin"]].head(3)

まとめ

xfeat を使って、タイタニックデータの特徴量エンジニアリング、特徴量探索を行ってみました。
Pandas だけを使うよりもシンプルでわかりやすく書けることが多かったので、今後も重宝しそうです。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

【データ分析】 Kaggle Masterと働きたい尖ったエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com

Ver.7.11からElasticsearchのスキーマ設計が大きく変わる

こんにちは、アクロクエストテクノロジー株式会社でElastic Stackのコンサルティング業務を担当している吉岡です。本記事は、Elastic Stack (Elasticsearch) Advent Calendar 2020 の14日目の内容になります。

本記事では、2020/10/13~2020/10/15に開催されたElastic ON Globalで、個人的に最もエキサイティングに感じたセッション「Schema on read with runtime fields」を紹介します。

Elastic ON Global
www.elastic.co

Schema on read with runtime fields

セッション概要

Elasticsearch has always been fast, but required structuring and indexing your data up front. We're changing that with the introduction of runtime fields, which enable you to extract, calculate, and transform fields at query time. They can be defined after data is indexed or provided with your query, enabling new cost/storage/performance tradeoffs, and letting analysts gradually define fields over time.

  • 発表動画

www.elastic.co

  • 発表資料

www2.slideshare.net

セッションを読み解くための重要キーワード

Schema on Write と Schema on Read

  • Schema on Write と Schema on Readの詳細にについては以下のブログが参考になります。

www.elastic.co

Runtime Field

これまで、Elasticsearchは主に「Schema on Write」のアプローチを採用してきました。検索/分析要件を先に決めて、Mappingを書いてからデータを投入する。スキーマレスと言いながら、最適な性能を得るには「Schema on Write」が必須でした。
Elasticsearch Ver.7.11から「Runtime Field」というデータ型が導入されます。一言で言うと、クエリ時にスクリプト(データ加工/抽出)を実行できるデータ型。スクリプトはMappingに記述(事前に定義)することも、クエリ自体に指定(実行時に定義)することも可能です。この「Runtime Field」と「非同期検索(Ver.7.7~)」を併用することで「Schema on Read」のユースケースが実現可能になります。

発表資料に関するコメント

以降はセッションの発表資料を抜粋しながらコメントしたいと思います。

f:id:acro-engineer:20201213182153p:plain:w800

  • Schema on Write と Schema on Readの比較です。
  • 左が従来の「Schema on Write」アプローチ。インデクシングする際にデータ加工を行うことで高速なクエリ/アグリゲーションを実現します。
  • 右が新しくサポートする「Schema on Read」アプローチ。インデクシング時にデータ加工をせず、ほぼ生データをロードする形になるためインデクシングは高速です。データ加工は必要に応じてクエリ毎に指定すればOK。これは、投入するデータの詳細がよく分かっておらず、事前のスキーマ(Mapping)作成が困難なシーンで特に有効です。

f:id:acro-engineer:20201213182208p:plain:w800

  • Runtimeフィールドを利用した場合「Schema on Write」と比較してインデックスサイズ(消費ストレージ)は小さく、インデクシングは高速になります。ただし、クエリの速度は大きく下がります。(クエリ時にデータ加工するので当然です)

f:id:acro-engineer:20201213182220p:plain:w800

f:id:acro-engineer:20201213182229p:plain:w800

  • Async search(非同期検索)での利用例。Rangeクエリで時間範囲を指定し、statusが「200」のドキュメントを検索しています。
  • Rangeクエリでヒットする全ドキュメントに対して「Mappingのstatusフィールドに定義されたスクリプトを実行し動的にstatusの値を生成、statusが200のドキュメントに絞り込むクエリ。Schema on Writeアプローチでの時間感覚(検索は100ms以下が当たり前)からするとスロークエリそのものです。
  • しかし、Schema on Readのユースケースはクエリの実行頻度が少ないシーンを想定しているので、非同期検索(デフォルトのタイムアウトは5日)でゆるやかに実行する形です。逆に、検索速度を求めるシーンでは、Runtimeフィールドではなく、通常のフィールドをSchema on Writeで使いましょう。

f:id:acro-engineer:20201213182242p:plain:w800

  • クエリ時にスクリプトを定義する場合のサンプル。「runtime_mappings」というパラメータに、ipフィールドを生成するスクリプトを記述しています。
  • MappingにRuntimeフィールドを定義していなくても、クエリで動的にフィールドを生成することができるのは非常に便利ですね。Schema on Writeではスキーマ設計/変更を管理者に集約する運用になると思いますが、Schema on Readでは各ユーザーがクエリを通して自由にスキーマ設計できることになります。


f:id:acro-engineer:20201213182256p:plain:w800

  • 将来的にはPainless以外にもGrokやその他のエンリッチをサポートするようです。

f:id:acro-engineer:20201213182327p:plain:w800

  • Schema on Readにおけるフィールドライフサイクルは以下のようになります。
    1. インデクシング時に@timestampだけを抽出し、時刻以外をmessageフィールドに入れておく。
    2. データを抽出したい時にクエリに抽出スクリプトを記述し、必要なデータ取得する(Mappingの変更が不要)。Schema on Writeでデータ加工をしようとすると、その都度Reindexが必要ですが、Schema on Readでは、データ加工の試行錯誤をするのにReindexが不要です。
    3. 頻繁に利用するフィールドは、通常のフィールド(Indexed Field)に移行する。具体的には、インデックステンプレートにIndexed Fieldを追加、次に生成される(日単位インデックスであれば翌日、月単位インデックスであれば翌月)インデックスからはSchema on Write用フィールドになります。

f:id:acro-engineer:20201213182340p:plain:w800

  • 通常の検索(左)と非同期検索(右)の仕様の比較。非同期検索は長時間かかるクエリを実行するもので、クエリが完了する前でも、途中までの検索結果を取得することが可能です。

f:id:acro-engineer:20201213182359p:plain:w800

  • まとめを意訳。
  • Schema on Readの導入により、Elasticsearchのスキーマ設計が大きく変わります。従来のように一気にスキーマを完成させてからデータ投入するのではなく、スキーマを大雑把に決めてデータ投入をしてしまい、必要に応じてフィールドを作成し、徐々にスキーマを確定していくことになります。また各フィールドは、検索、可視化、アドホックな調査、バッチ処理など、コンテキスト毎に設計するのが主流になるでしょう。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

【データ分析】
Kaggle Masterと働きたい尖ったエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com

コンテナ内のアプリが複数種類のログを出力する場合の収集方法

こんにちは。アキバです。

この記事は、Elastic Stack (Elasticsearch) Advent Calendar 2020 - Qiita の12日目です。
qiita.com

当社ではリモートワークに移行し始めて9~10か月になろうかというこの頃ですが、リモートワークをされている皆さんはどのような工夫をされているでしょうか?

私はiPad Proを日常的に使って、社内のホワイトボードでやっているような手書き体験の共有をしています。
最近、新しいペーパーライクフィルムを使用することになったのですが、これまでのペーパーライクと触感がガラリと変わり、慣れるまでちょっとぎこちない感じです。

今日は、そんな手書き体験の話とは全く関係ない、ログ収集に関する話です。

何の話か?

定期的に起きる質問

今の時代、アプリケーションをDocker/Kubernetesなどのコンテナ上で動作させることが多くなってきましたが、定期的に以下のような質問が出ます。

コンテナに載せたアプリケーションが複数のログを出力するような場合に、どうやってログを収集するのが良いのか?

コンテナを使わない場合は以下のような形でログ収集することができます。

f:id:acro-engineer:20201213140525p:plain

アプリケーションをコンテナに載せて動かそうとしたときに、通常はFilebeatはコンテナの外に置きたいと思います。
特に、Kubernetesの場合はFilebeatはDaemonSetで動かし、AutoDiscoverによってログ収集対象となるPodを(アプリコンテナの外で)選択します。

こうした構成で、以下のようにコンテナ内で複数種類のログを出力していると、単純にはコンテナ外からログが収集できないわけです。
(もっとも、コンテナ内にログを出力すること自体が悪手であり、ログを活用できないため意味のない行為ではありますが...)

f:id:acro-engineer:20201213140552p:plain

単純にログが1種類ならば、コンソール出力にしてcontainer inputで取得するのが定石かなと思いますが、コンソール出力は複数にできないので、何かしら策を講じる必要が出てきます。

理想的には

お作法的なことを言えば、1つのコンテナで動作するプロセスは1つであり、そのプロセスが出力する(収集対象となる)ログも1種類にしたいところです。
しかしながら、一部のアプリケーションには(アプリケーションの動作ログの他に)以下のようなログを出力するものがあります。

  1. アクセスログ - 自分のAPIが呼び出された際の記録(エンドポイント、ステータスコード応答時間など)
  2. 監査ログ - システム/アプリケーションの重要な操作(試行)の記録(操作/権限、ユーザ、成功or失敗など)

どのようにログ収集の仕組みを構築し、Elasticsearch+Kibanaで可視化するか?をパターンに分けて検討してみたいと思います。

※個人的な見解を含みます。もっと良い方法があるかもしれません。

コンテナ内のアプリが複数種類のログファイルを出力する場合の収集方法

方法1:ログファイルをコンテナ外から参照できるように共有する

Dockerであれば、Volume Mountを使ってホストのディレクトリをコンテナ(のログ出力ディレクトリ)にマウントする方法が可能です。
これにより、ホスト側で動作するFilebeatが、コンテナ内のプロセスが出力したログファイルを参照し、ログを収集することが可能になります。

f:id:acro-engineer:20201213140609p:plain

メリット
  1. コンテナが異常終了しても出力したログはホスト側に残るので、欠損(出力したログがElasticsearchに送られない現象)が発生しにくい
  2. アプリケーションはログ出力の方式(出力するファイルの数やフォーマット)を変更する必要がない
  3. ログの種類ごとのタグ付けはFilebeatで行い、投入先のElasticsearchのインデックス指定をLogstashで行う
  4. ログ加工が必要な場合は、Ingest PipelineまたはLogstashを使用可能
デメリット
  1. コンテナの可搬性は下がる(DockerComposeを使うことで一部吸収可能だが、ホスト側のディレクトリ準備やuidの統一など一部制約が生まれる)
  2. ホスト側でコンテナx収集対象ログファイル数のFilebeatを起動する必要がある
  3. Kubernetesの場合はこの方式は適用できない

方法2:Filebeatをコンテナ内に入れる

コンテナ内でFilebeatを動かせば、任意のログファイルを収集対象にすることができます。
複数のFilebeatを動かせば、複数のログファイルを収集することができますね。

f:id:acro-engineer:20201213140626p:plain

メリット
  1. コンテナの可搬性は高い(イメージに含まれているリソースのみでログ収集が完結する)→ Kubernetesでも実現可能
  2. アプリケーションはログ出力の方式(出力するファイルの数やフォーマット)を変更する必要がない
  3. ログの種類ごとのタグ付けはFilebeatで行い、投入先のElasticsearchのインデックス指定をLogstashで行う
  4. ログ加工が必要な場合は、Ingest PipelineまたはLogstashを使用可能
デメリット

Filebeatをコンテナ内に置くことでいくつかの注意点が発生します。

  1. 「どのコンテナから送られてきたログか」を示すには、コンテナ起動時にひと工夫が必要
    (それさえ出来れば、大きなデメリットではない)
    • filebeat.yml に add_docker_metadata の指定を行う
    • dockerがUNIXソケットで通信をしている場合、ホスト側の /var/run/docker.sock をマウント(TCP/IP通信の場合は、hostパラメータにIPアドレスを記載する)
    • 必要に応じて、rootユーザ権限を付与する
  2. コンテナの終了タイミングでFilebeatも終了してしまうため、一部のログがElasticsearchに送られず欠損する可能性がある
  3. Filebeatを入れて動かす分、わずかにコンテナサイズが肥大化する
  4. 設定がコンテナごとに存在するため、Filebeatの設定変更でコンテナの再作成(再起動)が必要になる

方法3:アプリでログの種類がわかるようにタグ付けし、すべてコンソールに出力する

アプリケーションがすべてのログをコンソール出力するように設定すれば、一つのFilebeatを使ってcontainer inputでまとめて収集することが可能です。

f:id:acro-engineer:20201213140641p:plain

メリット
  1. コンテナの可搬性は高い(ログ収集に関するリソースをアプリコンテナが持たない)→ Kubernetesでも実現可能
  2. コンテナが異常終了しても出力したログはホスト側に残るので、欠損(出力したログがElasticsearchに送られない現象)が発生しにくい
  3. コンテナの外でログ収集が可能なので、Filebeat側でコンテナを区別してタグ付けが可能
  4. コンテナにFilebeatを含める必要がないため、イメージの肥大化やライフサイクルの依存がない
デメリット
  1. アプリケーション側でログ出力先とフォーマット、特に種類がわかるようにするタグ付けが必要(初期の設計または設計変更が必要となる)
  2. 1つのFilebeatで処理をするため、設定できるタグや送信先は1つになる
    ログの種類ごとにElasticsearchのインデックスを分けたり、ログ加工が必要になる場合は、Logstashが必須(Ingest Pipelineだけでは実現できない)

共通:Kibanaでの可視化について

いずれの場合も、Elasticsearchに投入するまでの過程でタグ付けやログ加工、インデックスの振り分けを行えますので、Kibana(LogsやVisualize)で可視化する際の要求に対しては柔軟な設定が可能です。
(どの段階で加工・振り分けを行うか?の違いがあるため、システムの変更/構成管理のしやすさで制約が発生する可能性があります)

逆の言い方をすると、どのような可視化をするか?に沿ってこれらの処理を決める必要があります。
タグ付けやログ加工はそれほど頻繁に変わるものではないですが、コンテナのライフサイクルに影響を与えないことを考えると、Filebeatはコンテナの外にいることが望ましいと思います。

まとめ

Docker単独/Kubernetes上で動かすか、アプリケーション側の対応や変更が可能かによって選択肢が変わってくる部分があるかと思いますが、個人的には方法3がベターなのかなと思っています。
アプリケーション側で最初の設計さえ行えれば、それ以外のログ収集に関する要素をアプリケーション側から切り離すことができるのが最大のメリットだと思います。

ログ収集は後からでも始められますが、システム構築の設計段階からこういったことを考慮しておくと運用がしやすくなるのではないかと思いますので、参考にしていただければと思います。

それでは。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

世界初のElastic認定エンジニアと一緒に働きたい人Wanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com

kibanaのURL drilldownを使ってログ管理を効率化する

こんにちは。@Ssk1029Takashiです。
この記事は、Elastic Stack (Elasticsearch) Advent Calendar 2020の11日目です。
qiita.com

はじめに

Kibanaでアプリのログ管理をしていると、KibanaのDashboardだけを見ているだけではなく、実際のWebアプリの画面も確認することが多いです。
例えば、検索アプリの検索ログをKibanaでダッシュボードしている場合、急に検索回数が増えた単語などは、ユーザーがどんなコンテンツを探しているのか知るために実際の検索結果画面が見たくなります。

このような時に、DashboardとWebアプリの画面を行き来するのは、オペレーションの中で意外とストレスになります。
f:id:acro-engineer:20201206203709p:plain

そこで、Kibana 7.10から追加されたURL drilldownという機能を使うことで、より手軽にDashboardからWebページを参照できるようなります。

今回書かないこと

今回以下のことは記事の対象外にしています。

  • App Search・Elastic Stackの構築方法
  • Visualizeの作成方法

URL Drilldownとは

簡単に言うと、DashboardからクリックしたVisualizeの値をもとにしたURLへ遷移できる機能です。
URLはテンプレートを使って、クリックした値やVisualizeのタイトルなどを参照できます。

使い方として、以下のようにケースがあります。

  • 問題を絞り込んでクリックする際に、チケット管理システムのURLの特定フィールドに渡して起票する
  • ログ管理で表示したIPアドレスを外部のReputationサイトに渡して、そのページで結果を見てさらに詳しい情報を参照する。

IPアドレスをReputationサイトで調査する機能はSIEM UIに標準で実装されていますが、Dashboardからでも実現可能になります。

詳しくは以下のドキュメントを参照してください。
www.elastic.co

実際に試してみる

環境

今回はElastic Cloud上のApp Searchを検索アプリとして、URL drilldownを使った画面遷移を試してみます。
Elasticsearch:7.10
Kibana:7.10
App Search:7.10

また、今回は事前にApp Searchでこのブログのデータを検索できるようにしています。
構成としては以下のようになっています。
f:id:acro-engineer:20201211004519p:plain

Dashboardを作成する

まずは、App Searchのログを参照して、検索されたキーワードの頻度を出すVisualizeを作成して、Dashboardに設定します。
今回はLensのTreeMapを使用しています。
f:id:acro-engineer:20201207001405p:plain

URL drilldownを設定する

それではDashboardにURL drilldownを設定していきます。
Dashboardの編集画面から、URL drilldownを設定したいVisualizeを選択すると、「Create drilldown」というメニューが出てきます。
f:id:acro-engineer:20201207004241p:plain
選択すると、drilldown作成メニューが表示されます。
まず、Dashboard drilldownかURL drilldownのどちらを作成するかを選択する必要があるので、「Go to URL」を選択します。
f:id:acro-engineer:20201209231824p:plain
drilldown名とURLテンプレートを設定する画面なので、任意の名前と、URLを設定しましょう。
URLは以下のように検索サイトのURLにテンプレート文字列で選択した値がURLパラメータになるように設定します。
App Searchでは、URL引数にq=<検索キーワード>の形式で指定することで、検索できるので、以下のようにテンプレートを設定します

https://<検索サイトのURL>?q={{event.value}}

上記の{{event.value}}の部分にDashboardで選択した部分が表示している値が入ります。

上記を設定後、「Create drilldown」を選択して、URL drilldownを作成します。
設定後は、Dashboardを保存する必要があるので、忘れないようにしましょう。

Dashboardから検索ページに遷移する

保存後に、Dashbaord上でデータがある場所をクリックすると、以下のように、ポップアップが表示されるようになります。
f:id:acro-engineer:20201209234437p:plain

ポップアップのうち、URL drilldownをクリックすることで、Dashboard上で選択した値で検索されたページに飛ぶことができます。
f:id:acro-engineer:20201209235755p:plain

このように、Dashboard上でログを見ながら、スムーズにアプリでより詳細な情報を確認することができます。
無駄な画面移動や入力が減って、効率的になりましたね。

まとめ

本記事では、Kibanaの新機能であるURL drilldownを利用して、DashboardからWebアプリの画面にスムーズに移動できるようにすることで、日々の運用の効率化を図りました。
別のページを開きなおす、再度入力し直すなどの手間が減って、効率的になりそうです。
皆さんもDashboardでアプリログを運用するときにはぜひ参考にしてみてください。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

世界初のElastic認定エンジニアと一緒に働きたい人Wanted! - Acroquest Technology株式会社のデータサイエンティストの求人 - Wantedlywww.wantedly.com