再現性を担保したAWS Athenaでのランダムサンプリングについて(Advent Calendar Day 4)
はじめに
こんにちは、D2Cデータサイエンティストの小林です。
本記事では、AWS Athenaによるハッシュ関数を用いた再現性のあるサンプリング手法について検討します。
ハッシュについては以前の記事をご覧ください。
また、AWS AthenaでのDB作成やテーブル作成については触れませんので別の記事を参考にしていただければと思います。
開発環境
検証において以下の環境を使用しました。
AWS EC2 (インスタンスタイプ: r5.xlarge)
Python 3.10系(3.10.9)
awswrangler 2.6.0
pandas 1.5.1
random 3.10系で提供されているモジュール
string 3.10系で提供されているモジュール
ランダムサンプリングとは
ランダムサンプリングとは、無作為抽出とも呼ばれ、あるデータの中からランダムにデータの一部(サンプル)を抽出する方法のことです。
データ数が膨大な場合に対してよく用いられます。
ランダムサンプリングを適切に行えれば、膨大なデータの性質を一部のデータを調べるだけで把握することも可能です。
ただし、データ抽出に人間の意思や何かしらの意図が絡んでしまうと、適切な抽出を行えず分析の質が落ちてしまう可能性やランダムサンプリングしたデータに対する分析結果と、データ全てに対して分析を行った結果に誤差が生じる可能性があります。
再現性を担保してサンプリングする意味
上記で述べたように、データ分析をする際に、一部のデータから性質をみることがあります。
このような分析をする際に、何度抽出をしても同じ結果となるよう再現性が必要となる場合があります。
弊社では、機械学習を行う際に AWS Athenaを使用してデータを抽出することがあります。
この際に、ダウンサンプリングを行なった状態でデータを取得することがあります。
AthenaでランダムにデータサンプリングするSQLとしていくつかありますが、こちらは再現性がなくSQLを実行するたびに抽出されるデータ、件数が変わってしまいます。
このような場合、実験や検証の際に毎回結果が異なり正しい評価ができないことがあります。
もちろん、Athenaでデータを全て抽出しPythonなどで加工しSEEDを決めてサンプリングすれば再現性は担保されますが、データが膨大な場合Athenaで抽出する際にダウンサンプリングされていると実験が捗る場合があります。
よって、今回はAthenaで再現性のあるサンプリング手法について検討したいと思います。
実装と検証
サンプルデータの生成
import awswrangler as wr
import pandas as pd
import random, string
random.seed(818) # 乱数シードを818に設定
def randomname(n):
randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
return ''.join(randlst)
user_ids = []
age = []
gender = []
for i in range(0, 107):
user_ids.append(randomname(40))
age.append(random.randint(18, 80))
gender.append(random.randint(0, 2)) ### 0: 女性, 1: 男性, 2: 不明
### データフレームの作成
df = pd.DataFrame()
df["user_id"] = user_ids
df["age"] = age
df["gender"] = gender
### データの保存
df.to_parquet("sample.parquet")
上記データをAWS S3に保存し、Athenaで使えるようにします。
テーブルDDLは以下になります。
(db, prefix は適当な箇所を指定する必要があります。)
CREATE EXTERNAL TABLE `sample`(
`user_id` string,
`age` int,
`gender` int)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
's3://db/prefix'
Athenaによるランダムサンプリング手法1
query = '''
/* データの10%をランダムに取得するSQL */
SELECT
*
FROM
sample
tablesample bernoulli(10)
'''
df=wr.athena.read_sql_query(query, database=DATABASE, ctas_approach=False) ### 適当なDATABASEを指定
print(df)
print(len(df))
こちらを実行するとデータの10%をランダムに取得することができます。
しかし、実行のたびにデータと件数が変わってしまいます。
Athenaによるランダムサンプリング手法2
query = '''
WITH
sample_table AS (
SELECT
*,
RANDOM() AS rnd
FROM
sample
)
SELECT
*
FROM
sample_table
ORDER BY
rnd
limit 10
'''
df=wr.athena.read_sql_query(query, database=DATABASE, ctas_approach=False) ### 適当なDATABASEを指定
print(df)
print(len(df))
こちらを実行するとデータを10件(limitで指定した件数)ランダムに取得することができます。
しかし、実行のたびにデータが変わってしまいます。
データが変わってしまう原因として、RANDOM() AS rndの値が毎回変わってしまうことが挙げられます。
このrndの値がランダムに決まるが実行のたびに変わらないようにすることで再現性を担保したランダムな抽出ができます。
そこで、このrndの値をハッシュ関数を用いることで毎回データが変わることを防ぎます。
Athenaによるランダムサンプリング手法3
query = '''
WITH
sample_table AS (
SELECT
*,
from_big_endian_64(
xxhash64(CAST(user_id || '818' AS varbinary)) /*818: SEED*/
) AS hash_rnd
FROM
sample
)
SELECT
*
FROM
sample_table
ORDER BY
hash_rnd
limit 10
'''
df=wr.athena.read_sql_query(query, database=DATABASE, ctas_approach=False) ### 適当なDATABASEを指定
print(df)
print(len(df))
何度、実行しても結果が同じになると思います。
※ハッシュ化させるキーはユニークである必要がある。また、ハッシュの衝突についても考慮する必要がある。
Athenaにおけるハッシュ関数について詳しく知りたい方はこちらを参照してください。
まとめ
今回は、AWS Athenaでの再現性を担保したランダムサンプリングの手法について検討しました。
Athenaによるランダムサンプリング手法3で紹介した手法を用いることで再現性を担保することができます。
本記事が、Athenaを用いてデータを抽出する際の参考になれば幸いです。
また、他にもより良いやり方があると思いますので、ぜひコメント欄で教えていただければと思います。
最後までお読みいただきありがとうございました。
参考
採用情報
- D2Cグループ採用サイト
- D2C問い合わせフォーム
株式会社D2C d2c.co.jp のテックブログです。 D2Cは、NTTドコモと電通などの共同出資により設立されたデジタルマーケティング企業です。 ドコモの膨大なデータを活用した最適化を行える広告配信システムの開発をしています。
Discussion