BigQueryのテーブルのメタデータをCloud Data Catalogで管理する

自分が使いたいと思ったBigQuery上のリソース(tableやview)、内容を事前に完全に把握できている、ということは結構少ないのではないかと思います。そういったときに手助けをしてくれるのがメタデータです。BigQueryのリソースに対するメタデータを、Cloud Data Catalogのタグとして付与する方法を紹介します。Cloud Data Catalogを使うことで、分析者が必要なリソースに素早く辿り付いたり、正確な分析をするためのサポートができます。

Cloud Data Cagalogとは

BigQueryと比べると認知度は大分まだ低いと思うので、概要を先に紹介します。Cloud Data CagalogはGCPが面倒を見てくれるフルマネージドなメタデータ管理サービスです。フルマネージドというのがポイントで、チームにデータ整備人(データエンジニア)が一人しかいない!という状況でもオペレーションなど追加的な負荷少なく使うことができます。先日GAにもなったので、安心して使えますね。

BigQueryの{テーブル, カラム}のdescriptionにあれこれメタデータを書き込んでおけば、自動的にCloud Data Cagalogで検索できるようになります。(個人ではなく)チームでメタデータを整備していく体制を整える、という話題については先日ブログにも書きました。

BigQueryだけでなく、Cloud Pub/Sub、Cloud Storageのメタデータの管理もしてくれます*1

また、「このカラムは個人情報が入っているから、チーム内でも特定の人しかクエリ叩けないようにしたい」ということ、割とあると思いますが、そういったこともData Catalogを使うと簡単にできるようになります。

もう少し構造化してメタデータを保持したい: Tag Template

descriptionに書き込むのはいいのですが、全部テキストでフラットに情報を持つのは段々厳しくなってくると思います。「最後にクエリを打たれた人をDATETIME型で」とか「クエリを叩かれた回数をINT型で」持ちたいという欲求は自然と出てくると思います。Cloud Data CagalogではそれをTagとして表現でき、Tagにどういった情報を持たせるかの雛形(この項目はSTRING型で必須、などを指定できる)をTag Templateとして表現します。

私はバッチ処理の中でTagを洗い替えするので十分なケースだったので、すでにTag Templateが存在すれば消して新規に作るようにしました。

#!/bin/bash
set -ux

TAG_TEMPLATE=resource_usage_info
LOCATION=asia-northeast1

gcloud data-catalog tag-templates describe ${TAG_TEMPLATE} --location=${LOCATION}
if [ $? -eq 0 ]; then
  gcloud data-catalog tag-templates delete ${TAG_TEMPLATE} --location=${LOCATION} --force --quiet
fi

gcloud data-catalog tag-templates create ${TAG_TEMPLATE} --location=${LOCATION} \
  --display-name="Resource usage info" \
  --field=id=last_queried_person,display-name="Last queried person",type=string \
  --field=id=queries_counts,display-name="Queries count",type=double \
  --field=id=last_queried_at,display-name="Last queried timestamp",type=timestamp

必要なメタデータをAudit logから取得する

次に考えたいのは「最後にクエリを投げられた日」や「クエリを投げられた回数」といった情報をどこから取ってきてTagにするか、です。このような情報はAudit logからBigQueryで取得できるようにしておくと簡単です。マジで10分でできる。

結構色んな情報が入ってくるので、Tagを作るときにやりやすいよう、以下のSQLをviewとして保存しておきます。名前は何でもいいですが、my_project.my_mart.tables_last_accessed_atみたいな感じで。

Audit logの情報からクエリの統計量を計算するクエリ

WITH 
  raw_resource_usage AS (
    SELECT
      protopayload_auditlog.authenticationInfo.principalEmail,
      timestamp AS last_queried_at,
      info.resource,
      ROW_NUMBER() OVER (PARTITION BY info.resource ORDER BY timestamp DESC) AS rank,
    FROM
      `my_project.source__cloudaudit__bigquery.cloudaudit_googleapis_com_data_access`
    CROSS JOIN
      UNNEST(protopayload_auditlog.authorizationInfo) AS info 
    WHERE
      NOT REGEXP_CONTAINS(info.resource, r"LOAD_TEMP_") -- Embulk関係で呼ばれるものは計算に入れない
      AND NOT REGEXP_CONTAINS(info.resource, r"jobs")
  ),

  latest_access_by_resource AS (
    SELECT
      * EXCEPT(rank)
    FROM
      raw_resource_usage
    WHERE
      rank = 1
  ),

  resource_called_counts AS (
    SELECT
      resource,
      COUNT(*) AS count
    FROM 
      raw_resource_usage
    GROUP BY
      resource
    ORDER BY
      count DESC
  ),

  existing_tables AS (
    SELECT
      project_id,
      dataset_id,
      table_id,
      FORMAT("projects/%s/datasets/%s/tables/%s", project_id, dataset_id, table_id) AS table_full_name
    FROM (
      SELECT
        *
      FROM
        `my_project`.project__source__db.__TABLES__
      UNION ALL
      SELECT
        *
      FROM
        `my_project`.project__warehouse.__TABLES__
      UNION ALL
      SELECT
        *
      FROM
        `my_project`.project__mart.__TABLES__
    )
  )

SELECT
  dataset_id,
  table_id,
  principalEmail AS last_accessed_person,
  last_queried_at,
  resource_called_counts.count AS queries_counts
FROM
  existing_tables
INNER JOIN
  latest_access_by_resource
ON
  latest_access_by_resource.resource = existing_tables.table_full_name
INNER JOIN
  resource_called_counts
ON
  resource_called_counts.resource = existing_tables.table_full_name
ORDER BY
  last_queried_at

Tagとしてメタデータを登録

必要な情報がこのviewで簡単に取得できるようになったので、実際にTagとして登録していきます。

メタデータをTagとして登録するシェルスクリプト

#!/bin/bash
set -ux

PROJECT_ID=my_project
TAG_TEMPLATE=resource_usage_info
LOCATION=asia-northeast1
MY_TABLE=${PROJECT_ID}.my_mart.tables_last_accessed_at

bq query --max_rows 10000 --format json --use_legacy_sql=false "SELECT * FROM ${MY_TABLE}" | jq -c '.[]' > resource_usage_info.json

cat resource_usage_info.json | while read -r json; do
  echo $json | jq '
  {
    "last_queried_person": .last_accessed_person,
    "queries_counts": .queries_counts | tonumber,
    "last_queried_at": .last_queried_at
  }
  ' > tag_file.json

  DATASET=$(echo $json | jq -r .dataset_id)
  TABLE=$(echo $json | jq -r .table_id)
  ENTRY_NAME=$(gcloud data-catalog entries lookup "//bigquery.googleapis.com/projects/${PROJECT_ID}/datasets/${DATASET}/tables/${TABLE}" --format="value(name)")
  TAG_NAME=$(gcloud data-catalog tags list --entry "projects/${PROJECT_ID}/locations/${LOCATION}/entryGroups/@bigquery/entries/$(echo ${ENTRY_NAME} | tr '/' '\n' | tail -n 1)" --format="value(name)")

  if [ -n "$TAG_NAME" ]; then
    gcloud data-catalog tags delete ${TAG_NAME} --quiet
  fi

  gcloud data-catalog tags create \
    --entry=${ENTRY_NAME} \
    --tag-template=${TAG_TEMPLATE} --tag-template-location=${LOCATION} --tag-file=tag_file.json --format=json
done

これで完了です。こうしておくと、Cloud Data Catalogで検索した結果、このようにメタデータを登録することができます。このリソースの最近の利用状況、より詳しいことは誰に聞けばいいかといった情報が提供できるため、利用者がやりたかった分析に少し早く、より正確に到達できる状況にできるのではないかな、と思います。

*1:自分はまだBigQueryに対してだけしか使えていないけど

カスタマーサクセスのためのデータ整備人の活動記録というタイトルでオンライン登壇しました

第3回 データアーキテクト(データ整備人)を”前向きに”考える会という勉強会で、CREとしてデータ基盤を整備する活動についてオンライン登壇しました。

イベント登壇はまあまあやってきたはずなんですが、今回の登壇は初めて要素が満載でした。

  • CREとして初めての登壇
    • これまでは研究者 or アプリケーションエンジニアとして登壇
    • 今年の2月にCREになったばかりなので、私がCREについて語ってもいいんかいな...みたいなところはありますよね
      • と言いつつ、偉そうに語ってしまった
  • データ基盤に関する初めての登壇
  • 初めてのオンライン登壇
    • 意図せず(?)YouTuberデビューを果してしまった...!
    • ピーク時には400人以上(?)の方が聴講してくださっていた様子

かなり手探りだったのですがTwitterの様子を見ている限り、結構好評だったようでほっとしています。データ基盤に関するアウトプットは今後も定期的にしていきたいなと思っているので、引き続き頑張っていきます。

Twitter上でのみなさんのコメント

たくさんコメントを頂いて、嬉しかったので自分の発表に関するコメントを載せておきます。

MackerelのCREチームについて

データパイプラインの整備について

データ基盤でできること / 出せる価値を感じてもらうためにやったことについて

分析者が自走できる環境 / チームで継続的にメタデータを整備できる体制作りについて

データウェアハウスやデータマートのviewが壊れていないかvalidationする

データ基盤の小ネタです。

データ基盤におけるview

データ基盤でデータスキャンがそれほど大きくない場合、積極的にviewを使っています(BigQueryを利用しています)。日時のバッチでテーブルとして掃き出してもよいですが、バッチを回さずにクエリをガシガシ変更できるのがviewの魅力です。

BigQueryのviewは、新規保存や編集時にクエリがinvalidになっている場合は保存できないようになっています。しかし、保存後にviewが壊れてしまう場合が多々ありえます。例えば、参照していたテーブル / カラムがrename / 削除された、などです。

そんなに簡単には壊れないだろうと高を括っていると、痛い目に合います。仕事で管理しているデータ基盤のview、雑に作ったものを含めると数十個が壊れていることが分かりました...*1。データ基盤をBigQuery上で展開し始めてから半年でこれなので、何もせずに運用して数年後経った場合を考えると恐しいですね。

viewが壊れると...

viewが壊れてしまうと、当然チームに不都合が起きます。

  • 日頃見ていたダッシュボードが見れなくなる
    • 短期的に怖い
  • 既存のダッシュボードが壊れるのを恐れるために、viewのリファクタリングをせず似たようなクエリが乱発される
    • 中期的に怖い
    • ソフトウェアエンジニアとしてはviewもリファクタリングしてよりよいものにしていきたい
  • 壊れたviewが平気で転がっていると、割れ窓的に壊れたviewが増殖し始め、データ基盤が信頼できないものになる。その結果、データ基盤が使われなくなったり、データ分析が信頼されないものになる
    • 長期的に怖い

viewが壊れていないか監視する

様々なタイムスパンにおいて、viewが壊れるとよくないということが分かりました。では、viewが壊れないようにするにはどうすればよいでしょうか。壊さないためには基本的に触らない作戦が一番よいとは思いますが、viewをどんどんよいものにするためにも積極的に触るようにはしたいです。代わりに、触った結果viewが壊れていたら検知できるようにします。

bqのqueryサブコマンドには--dry_runというオプションがあり、invalidなクエリを投げると失敗してくれます。これを利用して、データウェアハウスやデータマートのviewが壊れていないかvalidateするスクリプトを用意しました。

#!/bin/bash
set -u

CWD=$(cd $(dirname $0); pwd)
PROJECT=my-project

DATASETS=("${PROJECT}:warehouse__dataset" "${PROJECT}:mart__dataset")

for dataset in ${DATASETS[@]}; do
  for view in $(bq ls --max_results=1000 --format=prettyjson $dataset | jq -r '.[] | select(.type == "VIEW") | .id'); do
    bq show --format json --view ${view} | jq -r .view.query | bq query --use_legacy_sql=false --dry_run --format=json > /dev/null
    if [ $? -ne 0 ]; then
      echo "${view} seems broken..."
      exit 1
    fi
  done
done

あとは毎日のバッチmkr wrapなどを使って、viewが壊れたら監視サービスですぐに分かるという状況を作っておきます。こうすることで、修正した後に悪影響があればすぐに分かるので、修正も怖くありません。

最近だったらgithub actionsとかに組み込んでも面白いかもしれませんね。「うちではこうやってvalidationしてるよ!」という例があったらどんどん教えて欲しい!

*1:スタートダッシュでどういうことができるかを含めてどんどん作っていこう!という方針で最初はやっていたので、想定内と言えば想定内。最初から完璧を目指しすぎて、全然使われない状況よりはよっぽどよいと思う

WFHの感想

特に結論はないです。今の時期にどういうことを感じていたかをテキストで残しておくと、10年後とかに見返したときに振り替えれるかなぁと思って。

よかった系

  • 自宅環境を整備しないといけなくなったので、少しだけ環境が整理された
  • スタンディングデスクは買って完全に正解だった。腰が痛くなったら立って、立つの疲れたら普通のデスクとして椅子に座って、というのを繰り返していると労働時間で腰が破壊されることがなくなった
    • 椅子は元からいいやつを持っている
    • 素足で立っていると足が疲れるので、ヨガマットを下に敷くと一時間くらいのmtgは余裕で行ける
  • オフィスのお菓子を食べられないので、おやつ体験BOX snaq.meというのを頼むようにしたけど、毎日の細やかな楽しみができてよかった
  • CREチーム自体は東京 / 京都 / 岡山でコロナ以前から他拠点でのコミュニケーションが日常だったので、慌ててどうこうする、ということは特になかった
    • リモート自体の難しさはもちろんあるけど、それはコロナ以前から特に変わらない、という認識をチームメンバーが持っていそう

悪かった / 困った系

  • 選択肢として在宅勤務があるのと、ディフォルトが在宅勤務なのは全然違う...
    • 基本的には会社で働きたい人間であることが分かった
  • オフィスランチがないのは大分困る。野菜の摂取量が減りがち
    • オフィスランチがないと昼休みが一瞬で溶けて全然休憩できない
  • 昼休みにSREとデザイナの雑談にちゃちゃ入れながら聞いているのが好きだったんだけど、そういうの難しくなってしまって寂しい感がある
  • そんなに張り切って仕事しているわけでもないのに、定時後になったらめちゃくちゃ疲れているときが週2くらいあって、晩御飯も食べずに爆睡してしまっているときがある
  • 髪いつ切りに行けばいいんだ
  • 事態がいつ頃収束するのか、この時期がいつまで続くのか全然見通しが付かないことが地味にストレス
  • 自分を含めてネットワーク環境が普段より厳しめで時々hangoutが途切れてスムーズに会話を進行できないときがある

「データ活用のための数理モデリング入門」を読みました

著者の一人からご恵贈いただきましたので、紹介してみたいと思います。

機械学習を中心としたデータ活用の敷居は下がってきているが...

10年ほど前と比べると、データ活用、特に機械学習を「使う」ハードルは以下のように下がってきています。

  • sklearnやPyTorchなどのライブラリを使えば、ある程度型にはまった問題は簡単に扱うことができるようになりました
  • 画像認識や自然言語処理の分野でも事前学習済みのモデルが配布されるようになりました
  • Amazon SageMakerやCloud AutoML など、コードを書かずとも機械学習のモデルが学習され、推論もできるマネージドサービスが普及し始めています

しかしながら、何かデータを入れれば何か結果を返してくれるブラックボックスとして使われてしまっている事例も残念ながら時々見かけます。どういった問題を解くかは、道具をどう「使う」かよりも何倍も重要です。

もちろん「使う」だけでなく「運用する」ための技術(MLOps)的な話題はありますが、本書のスコープ外の話題ではあるので、このエントリでは取り扱いません。

問題をどう数理的に捉えるか: 数理モデリング

「使う」のは簡単になってきた一方、課題となっている問題をどう数理的に捉えるか、というのは昔も今も重要なままですし、それほど簡単にはなっていないと思います。慣れない人からすると、数理的に捉える、というのはいかにも難しい響きですが、解決したい問題や解明したい現象がどういったプロセスを経て起こっているかを記述する道具、と私は捉えています。

今日のアイスクリームの売上はおおよそ今日の気温に比例する、というめちゃくちゃシンプルな線形回帰も立派な数理モデリングです。もちろん、現実はそれほど簡単ではないため、もっと複数の項目を考慮したモデルになる思いますが、回帰分析という数理モデリングとしては同じです。

現実の問題は、回帰分析だけで解決できるわけではもちろんありません。時には自分で問題設定を考え、数式を組み立てモデリングする必要に迫られるときもあるかもしれません。単にツールとしてライブラリを「使う」だけではなく、問題に合わせて自分で問題を設計できるところが数理モデリングの面白さの一つです。世の中の問題は似ているようで、それぞれ個性があります。個性にも色々あって

  • toB向けとtoC向けで扱うデータ量や分布、性質が異なる
  • 定常的なものではなく、時々刻々と変化していく
  • 単一の要素だけでは決まらず周囲の状況のよって予測結果が大きく変わる
  • 自社独自のXXXのデータと組み合わせるともっと深い知識が得られないか
  • 予測結果がよさそうなものばかりを推薦すると同じカテゴリのものばかりになってしまう。色んなカテゴリのものも含めてユーザーが飽きないようにもしたい

などが例えばあるでしょう。こういった自社のデータやビジネスに合ったものを提供したい場合に数理モデリングが必要になってきます。

過去の類似したアプローチを学ぼう

こうしたモデリングをゼロから自分でできるという人はそれほどいないでしょう*1。そういったときに役に立つのが過去の類似したアプローチです。個性溢れる問題とはいえ、似たような問題は過去に研究されていることが多いです。本書は様々な分野での数理モデリングのアプローチを紹介してくれる本です。

データ活用のための数理モデリング入門

データ活用のための数理モデリング入門

購買予測 / 離脱予測 / 資源配分 / オンライン広告 / 社会ネットワークなど様々な分野の問題において、どういった数理モデリングのアプローチが取られているかが解説されています。この本で書かれている数理モデルだけで問題が解決することは少ないかもしれませんが、数理モデリングのプロでもこういった基礎的なモデルをベースラインとして採用しつつ、少しずつ現象に合う / 説明できるモデルに洗練させていくものです。

データがなければ数値モデリングは片手落ちになってしまうことも多いですが、一方でデータも正しく活用されなければその価値を十分に発揮することは難しいです。自社に眠るデータの価値を存分に発揮させたい、という方、本書を手に取られてみてはいかがでしょうか。

関連

*1:いや、できる人もそれなりにいることは知っていますが、普通の人はそうではないでしょう...

最近の砂場活動その14: GoogleAnalytis For Firebaseのデータを使ってImplicit-feedbackな推薦システムを構築する

  • ここ半年ほどデータ分析やりまくっているのはいいんだけど、機械学習全然やってない
    • 仕事に不満があるわけでは全然ないけど、人間は欲張りなのであれこれやりたい
  • FirebaseのBigQuery Exportである程度データが溜まりつつある

ということで、タイトルの通り、趣味プロジェクトであるML-Newsに推薦システムを導入してみました、という内容です。特に目新しいことは書いてなくて、主に自分用のログ目的要素が強いです。

これまでのML-Newsの関連エントリの出し方とその問題点

これまでの関連エントリの出し方

ML-Newsは名前の通り機械学習に関するニュースサイトで、元々のエントリには機械学習っぽい単語が入っていることが多いです。作者の僕も機械学習の知識はあるため、以下の形で元のエントリに関連するエントリを出していました。

  • 機械学習に関連する単語を「根性で」列挙
  • Aho–Corasick algorithmを使って、タイトル中に含まれる機械学習に関連する単語を抽出
  • 抽出された単語を元にDBから文字列検索

大分ナイーブですね。まあでも、関連エントリを出すための初手としては悪くもないでしょう。

問題点

これまで説明した方法は以下の問題点がありました。

  • 機械学習に関連する単語を列挙し続けるのはしんどい...
    • 古典的なものは大分列挙したつもりだが、深層学習関連の手法は新しい単語や手法名がどんどん出てくる
    • 機械学習関係の研究を仕事でやっているわけではないので、正直キャッチアップしきれるわけがない
    • とはいえ、新しい単語を追加しないと単語のカバレッジが足りないこと起因で関連エントリが出せなかったり、納得性の低いエントリが関連エントリに出てしまう
  • 単語しか見ていないので、ユーザーの行動履歴の情報を関連エントリを出すロジックに含めることができない

Implicit-feedbackな推薦システムの導入

これらの問題点を解決するために、Implicit-feedbackな推薦システムを導入することにしました。この推薦は協調フィルタリングのアプローチの一種ですが、ユーザーによるアイテムのratingというexplicitなフィードバックを元に推薦するものではなく

  • ユーザーがアイテムを閲覧した
  • ユーザーがアイテムをお気に入りに追加した

といったimplicitな行動を元に推薦するアプローチです。有名な論文はCollaborative Filtering for Implicit Feedback Datasetsでしょうか。

データの取得

ML-Newsでもユーザーの閲覧履歴はFirebaseに溜めていっており、BigQuery Exportを使うことで簡単に学習データを得られます*1。Pythonだとこういう感じ。(事例id, ユーザーid, 閲覧回数)のタプルが手に入ります。

学習データを得るためのスクリプト

from google.cloud import bigquery
import pandas as pd

def get_firebase_training_data(client):
    query = """
    WITH user_example_pairs AS (
      SELECT
        user_id,
        CAST(REGEXP_EXTRACT(page_location, r"^https://www.machine-learning.news/example/([0-9]+)") AS INT64) AS example_id,
      FROM (
        SELECT
          event_date,
          event_name,
          (SELECT value.string_value FROM UNNEST(event_params) AS x WHERE x.key = "page_location") AS page_location,    
          (SELECT value.string_value FROM UNNEST(event_params) AS x WHERE x.key = "page_referrer") AS page_referrer,
          user_pseudo_id AS user_id,
        FROM
          `ml-news.my_firebase_dataset.events_2020*` )
      WHERE
        STARTS_WITH(page_location, "https://www.machine-learning.news/example/")
        AND event_name = "page_view"
    )

    SELECT
      example_id,
      user_id,
      COUNT(*) AS count
    FROM
      user_example_pairs
    INNER JOIN
      `ml-news`.source__db.example
    ON
      example_id = example.id
    WHERE
      ((example.label = 1) OR (example.score > 0.0) AND (example.label != -1))
    GROUP BY
      example_id, user_id
    """

    query_job = client.query(query)
    return query_job.to_dataframe().dropna()


bq_client = bigquery.Client()
data = get_training_data(bq_client)

学習 & 推薦

アルゴリズムの実装は今回の興味のスコープ外なので、よく使われているライブラリをそのまま使います。

学習を回して、あるエントリに対する関連エントリを計算するスクリプトはこんな感じ。

学習 & 関連エントリを出すスクリプト

user_idとitem_idはintにしておく必要があるので、LabelEncoderを使ってエンコードし、関連エントリで元のエントリのidが必要になったらデコードしています。

data = get_training_data(bq_client)
data['example_id'] = data['example_id'].astype("category")
data['user_id'] = data['user_id'].astype("category")

# intのidに相互変換が必要なので、LabelEncoderを使う
le = preprocessing.LabelEncoder()
le.fit(data['example_id'])

train = coo_matrix((data['count'].astype(float),
                   (le.transform(data['example_id']),
                    data['user_id'].cat.codes)))

model = implicit.als.AlternatingLeastSquares(factors=64, use_gpu=False)
model.fit(train, show_progress=False)

result = pd.DataFrame({'example_id' : [], 'related_example_id': []})

for target_example_id in set(data['example_id']):
  related = [int(item) for item in le.inverse_transform([k for k, v in model.similar_items(le.transform([target_example_id])[0], N=6)]) if item != target_example_id]
  tmp = pd.DataFrame({'example_id' : [], 'related_example_id': []})
  tmp['related_example_id'] = related
  tmp['example_id'] = int(target_example_id)
  result = result.append(tmp)

result['example_id'] = result['example_id'].astype(int)
result['related_example_id'] = result['related_example_id'].astype(int)

計算させるのはどこでもいいのですが、趣味でもk8sを動かすいいおもちゃが欲しいところだったので、GKE上のバッチで動かしています。n1-standard-1が1台で全然余裕で動く。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: recommend-related-examples
spec:
  schedule: "0 */1 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: recommend-related-examples
            image: gcr.io/my-ml-news/my-recommend_related_examples:latest
            imagePullPolicy: Always
          restartPolicy: OnFailure

うまく動かすための工夫

学習データを増やす

ML-Newsは細々としたサイトなので、学習データに使えるエントリの閲覧データはあまり多くありません。以下のデータを擬似的に閲覧データと見做すことで、学習データを増やしました。

  • Twiter上でユーザーがあるエントリに対して言及した
  • あるエントリに対してユーザーがブクマした

これは大分効いたようで、推薦結果が主観的にもよくなったなと感じました。

推薦対象のフィルタリング

クロールしてきたエントリ全てに対して推薦させると、流石に計算時間が厳しいです。「このエントリは機械学習に関連するか」を数年前から数万件自分でアノテーションしているので*2、そのデータを使って機械学習に関連しそうなエントリかを分類し、機械学習に関連しそうなエントリのみを対象にしました。データ取得のSQLでいうと、((example.label = 1) OR (example.score > 0.0) AND (example.label != -1))です。これで推薦対象を1 / 100以下に絞ることができるので、クラウド破産せずに済みます。

新規エントリに対する推薦

バッチで推薦を行なっているので、新規のエントリに対しては推薦を出すことができません。こういったエントリに対しては、閲覧回数上位のエントリを推薦するようにしました。よくあるアプローチですね。

推薦結果

うまくいっていそうな事例をいくつか見ていきます。

スクリーンショット 2020-04-10 1.00.45.png (631.4 kB)

スクリーンショット 2020-04-10 1.03.52.png (401.2 kB)

まとめ

DAUがぐっと伸びたりすると面白かったのですが、DAUは正直あまり伸びませんでした。まあ、他に効果大きそうな施策が普通にたくさんありそう...。ただ、サイト内の回遊率は上がっているので、多少はユーザーの興味に合ったエントリを出せているのかなぁと思ったりしています。

微妙な結果なので、これが仕事だと悲しい限りですが、いくら失敗しても痛くないのが趣味プロジェクトのいいところですね。どんどん失敗していこう。

*1:ややこしいデータ転送を考える必要なく、Webコンソールでぽちぽちやればいいだけなので助かる...!

*2:毎日通勤電車でぽちぽちやってる

最近買って or 使ってよかったもの

とりとめもなく書きます。

MacBook Pro

自分の金ではなく会社用のPC。これまでは13インチを使ってたけど、32GBの広大なメモリを求めて16インチのものを買った。画面が広くなると、大分世界が変わった。

もう10年以上、13インチのMacBookを使っていたので、16インチは画面もかなり広くなった。最初は体がなかなか馴染めなかったため、肩凝りに悩まされたが最近は大分マシになった(後述)。

iPad mini

これも買ったのは半年くらい前。ほぼ毎日使ってる。

iPad mini Wi-Fi 64GB - シルバー (最新モデル)

iPad mini Wi-Fi 64GB - シルバー (最新モデル)

  • 発売日: 2019/03/28
  • メディア: Personal Computers

  • サイズがちょうどよい
    • 布団の中で片手でギリギリ操作できる
    • 古いiPad Proも持ってるけど、両手で持たないと操作できないくらいにはデカすぎる
  • 小さいけど、windowを2分割できる
    • Kindle読みながらsafariで調べるのを一画面でできるのはよい
  • Sidecarでmacの画面を拡張できる
    • ミーティングの度に外部ディスプレイのケーブルに繋ぐのがダルくて仕方ない人間なので、外部ディスプレイをほとんど使わずに生活していた
    • Sidecarはワイヤレスでさっとつなげる。カフェとかでもぱっとつなげる
    • Slack.appとか常時見ておく必要はないけど、ちらっと見たいといったやつを出すのに重宝してる
    • あと、遠隔とのミーティングで自分が画面共有をしているけど、相手の様子も見ておきたいってときにSidecarに出しておけるのは便利
  • バッテリの持ちも十分
  • 価格はいくらか忘れたけど、十分ペイするくらい使い倒してる

Apple Pencilも買ったけど、充電が面倒なのとお絵描き用途があまりないので使わなくなってしまった。

Fit Boxing

MacBook Proによる肩凝りがいよいよマズい、しかしジムは絶対に続かない自信がある...ということで結構困っていたのだが、Fit Boxingを買ってやり始めるようになってから大分よくなった。

Fit Boxing(フィットボクシング)|オンラインコード版

Fit Boxing(フィットボクシング)|オンラインコード版

  • 発売日: 2019/07/04
  • メディア: Software Download

Fit Boxingのいいところはこんな感じ。

  • 気軽に始められる
    • Switchがあればいい
    • 外出用に着替える必要がない
    • そんなにスペースも必要としない
  • 運動量も自分でコントロールできる
    • 肩凝りがマシになればいいということで一番楽なやつを15分、週に2回くらいでやってる
  • あんまり難しくない
    • 音ゲーっぽいと聞いてたので、自分がどれだけできるか不安だったけど、当たり判定(?)が緩いのでなんとなくでやれてよい
      • わざとそういう風に設計されているっぽい
  • 間が空いてしまっても大丈夫
    • ストレッチの仕方から基本姿勢まで、毎回丁寧に説明してくれる
    • 何も覚えず画面を付ければokなので気軽に始められる
      • 昔ヨガを少しだけやっていたけど、レッスンの内容をチラチラ見ないと覚えられなかったので、長続きしなかった

コロナの影響で外出自粛になってきていて、全然運動をしなくなってる!!ということもあって、Fit Boxingはちょうどよい運動になっている。自宅で15分でできるので、お昼休みにちょっと体動かすかーという感じでできるのが気にいってる。

スタンディングデスク

これまでの不定期に自宅でリモートで仕事をしていたことはあったけど、頻度が少ないこともあり、リビングの机で騙し騙しでやっていた。コロナの影響で自宅勤務する機会が大幅に増える & 長期化するであろうということでちゃんとした机を買うことにした。

ちなみに、今の家に引っ越す前はIKEAのL字デスクを使っていて、これは結構よかった。が、場所を結構取る & IKEAの家具は解体が大変ということで処分してしまったのだった。

会社のチームの島にスタンディングデスクがあって、結構気にいってるので、自宅用にもスタンディングデスクを買うことにした。

ガス圧なので、簡単に高さを変えられるのがよい。こういう感じで使い分けてる。

  • Google Hangoutで1時間ミーティングをするときには高さを高くして、立って会話する
    • 意図的に座らない時間を作ることで腰が壊れるのを防ぐ
    • 運動量をちょっとでもマシにする
    • Fit Boxing用にSwitchを置くのにも丁度よい
  • 集中してコード書きたいときは高さを低くして、普通の机として利用する
    • 椅子は昔からアーロンチェアを使っている

机の面積はあまり大きくないが、↓くらいは余裕で置けるので十分。

  • MacBook Pro16インチ
  • iPad MiniをSidecarとして利用
  • ペットボトルのドリンク

ずっと立っていると足の裏が痛くなってくるので、ヨガ用のマットを下に敷いておくと長い時間のmtgも大丈夫になる。

デロンギ

空気が乾燥すると風邪引きやすくなる人間なので、なるべくエアコンを付けずにこれまで過ごしてたんだけど、引っ越した先はさすがに素の状態だと寒すぎる場所だった。しかし、空気は乾燥して欲しくない...というジレンマを解決してくれたのがデロンギのオイルヒーター。

こいつはマジでよい。

  • 空気が乾燥しない
  • あったかい
    • 以前は寒くて目を覚ます...というのがあったけど、デロンギを導入してからは寒くて目を覚ますということがなくなった
    • エアコンだと風がくるところだけ暖かい...ということがあるが、部屋全体がじんわり暖かくなるので、部屋のどこにいても寒いということがない
  • 静か
    • 静すぎるので、付けてることを忘れる

電気代はちょっと高いかもだけど、風邪引いてパフォーマンス落ちたときの時給を考えると余裕でペイする。もうなくては生きていけないやつ。

BigQuery

仕事でデータ基盤をほぼ一人で整備することになって、BigQueryを半年前から使い始めた。これは完全にライフチェンジングなサービスで、5年前から触っておきたかったなと思うと同時に、今BigQuery使い倒すタスクを仕事でバシバシやるチャンスがあるのはラッキーだなって思う。

  • 早い
    • ちょっとしたデータ分析をPostgreSQLでやっていたときと、世界観ががらっと変わった
    • PostgreSQLだとインデックス貼ってなければお菓子でも食べて休憩...とやっていた分析もBigQueryだと数秒で結果を返してくるのでお菓子を食べる時間がなくて困ってる
  • 安い
    • データ置いとく分のストレージも安いし、ガシガシ分析しまくっても自分の用途くらいだと毎月の本買う金のほうが高いわ、という感じ
    • あまりに安すぎてもうちょいお金払いたい
  • 簡単
    • BigQuery以外のデータソースをBigQueryに世界に持ってくるのが驚くほど簡単
    • GCPの画面でデータ転送の設定をすれば、 S3にあるログファイルをBigQueryに持ってこれる
      • 定期的に差分だけ
    • FirebaseのBigQuery Exportでイベントデータも定期転送できて、バシバシしばける
    • 営業や事務の人とのデータやり取りはスプレッドシートで行なうことも多いが、スプレッドシートともJOINできてしまう...

BigQueryじゃないけど関連で要望があって、Data CatalogがもっとよくなるとBigQueryの体験もさらによくなるなぁと思う。

  • Data Catalogで設定したビジネスメタデータの情報をBigQueryのテーブルやviewの説明のように載せて欲しい
  • Data Catalogの検索で「このテーブルを参照しているviewのSQL」も引っかかるようにして欲しい
    • viewのリファクタリングにめちゃくちゃ使いたい
  • テクニカルメタデータでlast_accessed_atも出して欲しい
    • 使われていないものは消していきたい。今はaudit logを集計して出しているけど、そこまでmanagedで出してくれると手間が省けて助かる