LoginSignup
14
12

"気の利いた"擬似データを生成したい

Last updated at Posted at 2021-12-22

この記事は

NSSOL advent carendar 12/23担当分です。よろしくお願いします。

昨日は研修を運営してみて思ったことでした。
研修対応すると、その後も割と忙しくて、振り返りの時間がちゃんと取れなかったりします。
まとまった現場知見・感想が読めるのって、ありがたいなと思いました。

さて、今回のテーマは、「疑似データ生成」です。

背景:実データの取得は大変 擬似データが使えるかも

データ分析やシステム開発のために、実データかそれに近いデータが欲しくなることは多々あります。
ただ、顧客情報や営業秘密といった機微な情報が含まれる場合は、データ取得までに高いハードルがあることが多いです。
結果、試してみたいアイディア/製品/分析手法などの適用ができないこともあるかと思います。

スクリーンショット 2021-12-22 20.06.22.png

解決策の1つとして、擬似データの利用、が挙げられそうです。参考
実データを入力して、データの形式や統計量・分析結果など保存してほしい性質を残しつつ、実データとは一定以上異なる安全な擬似データ...を生成することで、

  • 有用性の確保:システム開発・分析のPoCに必要な、実データに近いデータの確保
  • 安全性の確保:実データの提供による情報漏洩事故の回避

ができるかもしれません。1

スクリーンショット 2021-12-22 20.06.29.png

擬似データ生成手法と課題

数多くの擬似データ手法が提案・製品化されています。2

擬似データ生成を行える製品・ソフトウェア (調査中)

  • システムエグゼの テストエース
    • システム開発用のテストデータ生成用のツールです
    • ダミー氏名や、ダミー郵便番号とそれに対応したダミー住所、までやってくれるのは便利そうですね
    • 「数値のランダム変換」などを行うと、統計的な性質は失われそうです。分析用途なら、他の仕組みが適していそうです
  • Mostly AIの Synthetic Data Platform
    • 擬似データ生成のツールです。元データからは一定以上異なり、かつ統計的な性質を上手く保持したデータを生成してくれます
    • データ分析・活用のPoC用のデータ生成、に利用できそうです。
    • 擬似データ生成コンテストPWSCUP2020でも好成績でした。
  • NTTのTasokarena
    • 匿名加工を行うツールです。
    • 最近、擬似データ生成の手法が実装されたみたいです。参考
  • etc..

擬似データ生成手法 (調査中)

GANベースの手法か、確率モデルベースの手法か、で一旦分類してみています。

擬似データ生成の課題

擬似データの品質が気になることがあります。

例えば、「年齢」「職業」という項目を含むデータを擬似生成した時、「1歳、会社役員」というデータは、あまり含んでほしくありません。
(不安に思って調べてみたら、会社役員は10歳からOKらしいです。知りませんでした...)

yakuin.PNG

他にも、ID列に重複があったり、給料がマイナスの値があったりしても困ります。業務ルールや常識を反映したい場面がありそうですが、列ごとに確率分布からサンプリングしたりすると、こういうあり得ないデータが頻繁に生成されてしまいます。

擬似データにも現実味を持たせるために、ある程度の「制約」を反映する方法を探したくなりました。

今回やること:SDVを使って、制約を反映した擬似データを生成する

常識や業務的なルールを反映...しやすそうな仕組みを持つ擬似データ生成の仕組みとして、「SDV」というライブラリを見つけました。

少しだけ触ってみます。

チュートリアルを参考に、実際に動かしてみます。ノートブックをColabで用意してみましたので、もしご興味ありましたら動かしてみてください。

Open In Colab

実験:SDVによる制約付き擬似データ生成

SDVのインストール

pipから導入できます。便利ですね。

! pip install sdv
from sdv.demo import load_tabular_demo
import pandas as pd

デモデータを参照します。

employees = load_tabular_demo()
print(employees.columns)
employees.head()
company department employee_id age age_when_joined years_in_the_company salary annual_bonus prior_years_experience full_time part_time contractor
0 Pear Sales 1 39 36 3 83500.00 21000.00 5 1.0 0.0 0.0
1 Pear Design 5 36 29 7 103581.36 23922.77 4 0.0 0.0 1.0
2 Glasses AI 1 37 34 3 112000.00 19500.00 2 1.0 0.0 0.0
3 Glasses Search Engine 7 35 26 9 82160.76 24167.84 2 0.0 0.0 1.0
4 Cheerper BigData 6 40 38 2 159500.00 14500.00 5 0.0 1.0 0.0

複数社の従業員の給料・雇用形態データです。こちらは当然疑似データです。

データのルールの確認

制約を反映した擬似データを生成してみます。

よく観察してデータの意味を考えると、いくつかルールがありそう。例えば...

  • 一意性の制約
    • $\text{company}$(会社)ごとに一意な$\text{employee_id}$(社員ID)が振られるはずなので、「($\text{company, employee_id}$)は一意」
  • 不等式で表現できる制約
    • $\text{salary}$の値を考えると適当な範囲に収まるので、「$0 \leq \text{salary} \leq 2,000,000$」
    • $\text{age}$(今の年齢)と$\text{age_when_joined}$(入社した時の年齢)の関係を考えると、「$\text{age} \geq \text{age_when_joined}$」
    • $\text{age}$は年齢なので、従業員っぽい年齢(一旦16歳から80歳までとします)なはずなので、「$16\leq\text{age}\leq 80$」
  • 等式で表現できる制約
    • 値の関係を考えると、「$\text{age} - \text{age_when-joined} = \text{years_in_the_company}$」
  • そのほかいろいろ (分類するのが面倒になりました)
    • $\text{full_time}$, $\text{part_time}$, $\text{contractor}$は、どれかが1で他が0になっている(3種類の値が含まれる契約形態列をone-hot-encodingしたもの、という背景があるみたいです)

などがあります。 3

SDVにはさまざまな種類の制約を反映した擬似データを作る...ための仕組みが用意されています。それぞれ実装して疑似データに反映してみます。 4

データ制約の実装・反映

実装例を一部載せておきます。実装の詳細はColabに書くことにします。

一意性制約の反映

sdv.constraints.Uniqueを使います。
一意になる列の組を入力します。今回だと、会社名と従業員idです。

from sdv.constraints import Unique
unique_employee_id_company_constraint = Unique(
    columns = ['company', 'employee_id'],
)

他にも、いろいろ制約を実装してみました。
記事が長くなってしまうので、詳細が気になる方はColabを見てください。
Open In Colab

制約の反映・データ生成

データ生成の手法は、今回はCTGANを使います。SDVに実装されている他のデータ生成手法(TGAN、Copula、etc...)でも、同じように使えます。

試しに、一部制約を反映しない版も準備しておいて、生成されたデータを比較してみます

## 制約の反映
from sdv.tabular import CTGAN

## 実装してみた制約一覧
constraints = [
               unique_employee_id_company_constraint, ## 会社ごとにIDが一意ですよ制約
               age_gt_age_when_joined_constraint, ## ageはage_when_joinedより大きいぞ制約
               reasonable_salary_constraint, ## salaryは適当な範囲(0-2,000,000)だぞ制約
               years_in_the_company_constraint, ## 在籍年数は、年齢-入社年齢だぞ制約
               reasonable_age_constraint, ## 年齢は、適当な範囲(16-80)だぞ制約
               one_hot_constraint, ## 契約形態の3列はone-hot-encodingで作ったよ制約
]

## 一部の制約を外す:在籍年数と、one-hot-encodingの制約を外してみた版
constraints_without_formula = [
               unique_employee_id_company_constraint, ## 会社ごとにIDが一意ですよ制約
               age_gt_age_when_joined_constraint, ## ageはage_when_joinedより大きいぞ制約
               reasonable_salary_constraint, ## salaryは適当な範囲(0-2,000,000)だぞ制約
               # years_in_the_company_constraint, ## 在籍年数は、年齢-入社年齢だぞ制約
               reasonable_age_constraint, ## 年齢は、適当な範囲(16-80)だぞ制約
               # one_hot_constraint, ## 契約形態の3列はone-hot-encodingで作ったよ制約
]


## 学習・データ生成
## 制約なし版
ct = CTGAN()

## 制約を全て反映した版
ct_constraint = CTGAN(constraints=constraints)

## 制約を一部反映していない版
ct_constraint_ = CTGAN(constraints=constraints_without_formula)

## 学習
ct.fit(employees)
ct_constraint.fit(employees)
ct_constraint_.fit(employees)

## データ生成
sampled_ct = ct.sample(100)
sampled_ct_constraint = ct_constraint.sample(100)
sampled_ct_constraint_ = ct_constraint_.sample(100)

さて、データを確認してみましょう。

データの確認

制約なし版

## 内容確認
sampled_ct.head()
company department employee_id age age_when_joined years_in_the_company salary annual_bonus prior_years_experience full_time part_time contractor
0 Glasses Support 80 33 33 1 144000.00 19900.27 2 0.0 0.0 1.0
1 Cheerper BigData 79 45 38 5 144000.00 10640.13 5 0.0 0.0 0.0
2 Pear Sales 1 37 30 9 138608.64 19900.27 1 0.0 1.0 0.0
3 Pear Support 1 40 35 8 58648.49 19900.27 5 1.0 0.0 1.0
4 Cheerper Design 68 33 31 2 86882.62 9930.32 5 0.0 0.0 1.0

2,3行目で、(会社、社員ID)の(Pear, 1)が重複していて、一意になっていません。困ります。

では、ちゃんと制約を反映した版を見てみましょう。

制約を反映した版

## 内容確認
sampled_ct_constraint.head()
company department employee_id age age_when_joined years_in_the_company salary annual_bonus prior_years_experience full_time part_time contractor
0 Pear Support 1 44 30 14 50328.417643 18698.33 2 0.0 1.0 0.0
1 Pear AI 3 39 30 9 27977.662781 12458.97 2 1.0 0.0 0.0
2 Pear Search Engine 43 49 39 10 29318.168727 19900.27 4 1.0 0.0 0.0
3 Glasses Design 21 45 30 15 27384.865234 19900.27 5 0.0 1.0 0.0
4 Cheerper Sales 1 46 30 16 33498.534931 19900.27 4 0.0 1.0 0.0

おお、できていそうです。
IDの制約と、age,age_when_joined,years_in_the_companyの関係と、one-hot-encodingもちゃんと反映されていそう。

では、一部だけ制約を外した版を見てみます。

制約を一部反映していない版

## 内容確認
sampled_ct_constraint_.head()
company department employee_id age age_when_joined years_in_the_company salary annual_bonus prior_years_experience full_time part_time contractor
0 Pear BigData 1 50 34 6 66627.734809 15059.80 4 1.0 0.0 1.0
1 Cheerper Design 1 41 30 5 25734.434223 19900.27 3 1.0 1.0 1.0
2 Cheerper Search Engine 8 38 35 2 71856.151940 19900.27 2 0.0 0.0 0.0
3 Glasses Support 14 40 34 1 48165.690454 19900.27 3 0.0 1.0 1.0
4 Glasses Sales 1 31 30 6 104812.032219 12291.97 3 0.0 1.0 0.0

ふむ...years_in_the_companyがルールにあっていません。

右端3列のone-hot-encodingルールも満たしていませんね。

一旦おしまいです。

感想

制約を実装して反映する...ところまでは、できました。

実務的には、都度実装するのはちょっと大変です。
DBMSでテーブルを定義した際、業務ルールを今回のように制約として実装しておいて、データ生成が必要な時には呼び出すだけ...という運用はあり得るかも。
リレーションを考慮したデータ生成もできたりしますし。

データベース側の外部キー制約やcheck制約を読み取って、SDVでの制約の記述を自動生成できたりすると、楽に擬似データが作れそうです。

次にやること

今後技術的に調査したいこととしては...

  • 実データ vs 制約反映済み擬似データで、
    • 記述統計の比較
    • 目的を絞ったデータ分析・機械学習の結果の比較
    • 安全性の検証
  • 効率的にデータ生成を行えるような制約の書き方
    • 制約が増えてくると、学習・生成に時間を要しそうです
    • reject-sampling:ルールに合わないデータを除外する手法を使いすぎると、サンプリングの回数がネックになる?
  • データ生成の制約と、差分プライバシー等安全性基準の組み合わせ

があります。

差分プライバシー基準での安全性と、業務ルールの反映を両立する...という研究は、既にあるかな?調べてみます。

以上です。

明日は

@kmnky さんの投稿です!楽しみですね!

  1. 今回はfakerのような、ゼロから擬似データを作る、というアプローチは考えません。データを入力とする疑似データ生成を対象にします。

  2. ここで挙げた製品群や手法について、2021年現在当社が販売・導入等のご支援を行えるわけではありません すみません がんばります

  3. 擬似データの用途次第では、無視しても問題ない制約もあると思います。

  4. 前処理や後処理で対応する方が良さそうなもの...もあると思います。ただ、管理の手間の観点で、データのルールを全部擬似データ生成のところに反映しておく、という戦略もありそうです。処理内容が散在していると管理がめんどくさそうなので。

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12