この記事は
NSSOL advent carendar 12/23担当分です。よろしくお願いします。
昨日は研修を運営してみて思ったことでした。
研修対応すると、その後も割と忙しくて、振り返りの時間がちゃんと取れなかったりします。
まとまった現場知見・感想が読めるのって、ありがたいなと思いました。
さて、今回のテーマは、「疑似データ生成」です。
背景:実データの取得は大変 擬似データが使えるかも
データ分析やシステム開発のために、実データかそれに近いデータが欲しくなることは多々あります。
ただ、顧客情報や営業秘密といった機微な情報が含まれる場合は、データ取得までに高いハードルがあることが多いです。
結果、試してみたいアイディア/製品/分析手法などの適用ができないこともあるかと思います。
解決策の1つとして、擬似データの利用、が挙げられそうです。参考
実データを入力して、データの形式や統計量・分析結果など保存してほしい性質を残しつつ、実データとは一定以上異なる安全な擬似データ...を生成することで、
- 有用性の確保:システム開発・分析のPoCに必要な、実データに近いデータの確保
- 安全性の確保:実データの提供による情報漏洩事故の回避
ができるかもしれません。1
擬似データ生成手法と課題
数多くの擬似データ手法が提案・製品化されています。2
擬似データ生成を行える製品・ソフトウェア (調査中)
- システムエグゼの テストエース
- システム開発用のテストデータ生成用のツールです
- ダミー氏名や、ダミー郵便番号とそれに対応したダミー住所、までやってくれるのは便利そうですね
- 「数値のランダム変換」などを行うと、統計的な性質は失われそうです。分析用途なら、他の仕組みが適していそうです
-
Mostly AIの Synthetic Data Platform
- 擬似データ生成のツールです。元データからは一定以上異なり、かつ統計的な性質を上手く保持したデータを生成してくれます
- データ分析・活用のPoC用のデータ生成、に利用できそうです。
- 擬似データ生成コンテストPWSCUP2020でも好成績でした。
- NTTのTasokarena
- 匿名加工を行うツールです。
- 最近、擬似データ生成の手法が実装されたみたいです。参考
- etc..
擬似データ生成手法 (調査中)
GANベースの手法か、確率モデルベースの手法か、で一旦分類してみています。
- GANベースの手法 (**GAN)
- MedGAN [arXiv:1703.06490][GitHub]
- TableGAN [arXiv:1806.03384][GitHub]
- TGAN [arXiv:1811.11264][GitHub]
- 触ってみた記事もありました
- 分析用データへの擬似データの追加、を試されています
- CTGAN [arXiv:1907.00503][GitHub]
- 触ってみた記事もありました。
- 擬似データの追加により、XGBoostによる分析にどのような影響が出るか、を検証されています
- 確率モデルベースの手法
- その他
-
統計値を用いたプライバシ保護擬似データ生成手法
- 上述のNTTの匿名加工ツールにも実装されていそうです
-
統計値を用いたプライバシ保護擬似データ生成手法
- etc..
擬似データ生成の課題
擬似データの品質が気になることがあります。
例えば、「年齢」「職業」という項目を含むデータを擬似生成した時、「1歳、会社役員」というデータは、あまり含んでほしくありません。
(不安に思って調べてみたら、会社役員は10歳からOKらしいです。知りませんでした...)
他にも、ID列に重複があったり、給料がマイナスの値があったりしても困ります。業務ルールや常識を反映したい場面がありそうですが、列ごとに確率分布からサンプリングしたりすると、こういうあり得ないデータが頻繁に生成されてしまいます。
擬似データにも現実味を持たせるために、ある程度の「制約」を反映する方法を探したくなりました。
今回やること:SDVを使って、制約を反映した擬似データを生成する
常識や業務的なルールを反映...しやすそうな仕組みを持つ擬似データ生成の仕組みとして、「SDV」というライブラリを見つけました。
少しだけ触ってみます。
チュートリアルを参考に、実際に動かしてみます。ノートブックを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を見てください。
制約の反映・データ生成
データ生成の手法は、今回は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 さんの投稿です!楽しみですね!