পাঠ ১২ · ৫৭-এর মধ্যে · মডিউল ৩

অ্যানোনিমাইজেশন ও k-anonymity

Anonymization & k-anonymity
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ডিরেক্ট আইডেন্টিফায়ার বনাম কোয়াসি-আইডেন্টিফায়ার — কেন শুধু নাম মুছে ফেলা যথেষ্ট নয়
  • k-anonymity-র সংজ্ঞা এবং এটি কীভাবে "রি-আইডেন্টিফিকেশন ঝুঁকি"-কে একটি পরিমাপযোগ্য সংখ্যায় রূপান্তর করে
  • জেনারেলাইজেশন (generalization) কৌশল — এক্সাক্ট মান থেকে ব্র্যাকেট/রেঞ্জে সরিয়ে গ্রুপ সাইজ বাড়ানো
  • Python দিয়ে একটি সত্যিকারের গ্রুপ-সাইজ হিসাব, আগে ও পরে — জেনারেলাইজেশনের প্রকৃত প্রভাব দেখা

১ · কোয়াসি-আইডেন্টিফায়ার সমস্যা

একটি ডেটাসেট থেকে নাম, ইমেইল, ফোন নম্বরের মতো ডিরেক্ট আইডেন্টিফায়ার মুছে ফেলাই যথেষ্ট মনে হতে পারে — কিন্তু বাস্তবে তা নয়। বয়স, জিপ কোড, ও লিঙ্গের মতো ফিল্ড এককভাবে কাউকে চিহ্নিত করে না, কিন্তু এই তিনটি একসাথে জানা থাকলে প্রায়ই একজন নির্দিষ্ট ব্যক্তিকে বাকি জনসংখ্যা থেকে আলাদা করে ফেলা সম্ভব — এই ফিল্ডগুলোকে বলা হয় কোয়াসি-আইডেন্টিফায়ারQuasi-identifierএকটি ফিল্ড যা এককভাবে কাউকে চিহ্নিত করে না, কিন্তু অন্য কোয়াসি-আইডেন্টিফায়ারের সাথে মিলে একজন ব্যক্তিকে অনন্যভাবে (বা প্রায় অনন্যভাবে) শনাক্ত করে দিতে পারে — যেমন বয়স + জিপ কোড + লিঙ্গ।। এই ধরনের রি-আইডেন্টিফিকেশন গবেষণায় বহুবার প্রমাণিত হয়েছে — এমনকি "শুধু নাম মুছে ফেলা" ডেটাসেট থেকেও প্রকাশনার তারিখ, বয়স ও পোস্টাল কোডের মতো সাধারণ ফিল্ড ব্যবহার করে নির্দিষ্ট ব্যক্তি শনাক্ত করা সম্ভব হয়েছে বলে প্রাইভেসি গবেষণা সাহিত্যে সুপ্রতিষ্ঠিত।

২ · k-anonymity-র সংজ্ঞা

k-anonymityk-Anonymityএকটি ডেটাসেট k-অ্যানোনিমাস যদি কোয়াসি-আইডেন্টিফায়ারের প্রতিটি ইউনিক কম্বিনেশনের গ্রুপে অন্তত k সংখ্যক রেকর্ড থাকে — অর্থাৎ যেকোনো একজন রেকর্ড অন্তত k-1 জন অন্যের মধ্যে অবিচ্ছেদ্য (indistinguishable)। একটি ডেটাসেটকে k-অ্যানোনিমাস বলা হয় যদি কোয়াসি-আইডেন্টিফায়ারের প্রতিটি সম্ভাব্য কম্বিনেশনের জন্য অন্তত kটি রেকর্ড সেই কম্বিনেশন শেয়ার করে। যদি কোনো কম্বিনেশনে মাত্র ১টি রেকর্ড থাকে, সেই ব্যক্তিকে অন্য কোনো তথ্য (যেমন একটি সর্বজনীন ভোটার তালিকা) দিয়ে সহজেই শনাক্ত করা সম্ভব হতে পারে।

এক্সাক্ট বয়স + জিপ গ্রুপ সাইজ = ১ (অনন্য) জেনারেলাইজেশন বয়স-ব্র্যাকেট + জিপ৩ গ্রুপ সাইজ = ৩ (k=3 নিরাপদ) A একা — সহজে শনাক্তযোগ্য ৩ জনের মধ্যে অবিচ্ছেদ্য
এক্সাক্ট কোয়াসি-আইডেন্টিফায়ার প্রায়ই প্রতিটি রেকর্ডকে অনন্য করে ফেলে (গ্রুপ সাইজ ১); জেনারেলাইজেশনের মাধ্যমে একই তথ্যের একটি কম নির্দিষ্ট সংস্করণ ব্যবহার করলে গ্রুপ সাইজ বেড়ে k-anonymity অর্জন করা যায়।

৩ · কোড দিয়ে আগে/পরে হিসাব

নিচের কোড সেলে ১২ জনের একটি সিন্থেটিক ডেটাসেট নেওয়া হয়েছে, প্রতিটি রেকর্ডে বয়স, জিপ কোড ও লিঙ্গ (কোয়াসি- আইডেন্টিফায়ার) আছে। প্রথমে এক্সাক্ট মান দিয়ে গ্রুপিং করা হবে, তারপর বয়সকে দশকের ব্র্যাকেটে (যেমন ৩২ → "30s") এবং জিপ কোডকে প্রথম ৩ ডিজিটে জেনারেলাইজ করে আবার গ্রুপিং করা হবে — আসল k=3 চেক দুইবার চালিয়ে সত্যিকারের সংখ্যা তুলনা করা হবে।

Python
# একটি সিন্থেটিক, ইলাস্ট্রেটিভ ১২-রেকর্ডের ডেটাসেট -- বাস্তব কোনো ব্যক্তির তথ্য নয়
records = [
    {"id": "R1",  "age": 32, "zip": "10001", "gender": "M"},
    {"id": "R2",  "age": 35, "zip": "10002", "gender": "M"},
    {"id": "R3",  "age": 38, "zip": "10003", "gender": "M"},

    {"id": "R4",  "age": 41, "zip": "20001", "gender": "F"},
    {"id": "R5",  "age": 44, "zip": "20002", "gender": "F"},
    {"id": "R6",  "age": 47, "zip": "20003", "gender": "F"},

    {"id": "R7",  "age": 61, "zip": "30001", "gender": "M"},
    {"id": "R8",  "age": 63, "zip": "30002", "gender": "M"},
    {"id": "R9",  "age": 65, "zip": "30003", "gender": "M"},

    {"id": "R10", "age": 21, "zip": "40001", "gender": "F"},
    {"id": "R11", "age": 24, "zip": "40002", "gender": "F"},
    {"id": "R12", "age": 27, "zip": "40003", "gender": "F"},
]

def age_bracket(age):
    return f"{(age // 10) * 10}s"   # যেমন 32 -> "30s"

def zip_prefix(zip5):
    return zip5[:3]                 # যেমন "10001" -> "100"

def group_by_qi(recs, qi_fields):
    """কোয়াসি-আইডেন্টিফায়ার ফিল্ডের কম্বিনেশন অনুযায়ী রেকর্ড গ্রুপ করে।"""
    groups = {}
    for r in recs:
        key = tuple(r[f] for f in qi_fields)
        groups.setdefault(key, []).append(r)
    return groups

def k_anonymity_report(recs, qi_fields, k):
    groups = group_by_qi(recs, qi_fields)
    sizes = {key: len(v) for key, v in groups.items()}
    min_size = min(sizes.values())
    violating = [r["id"] for grp in groups.values() if len(grp) < k for r in grp]
    return len(groups), min_size, violating

# ধাপ ১: এক্সাক্ট কোয়াসি-আইডেন্টিফায়ার দিয়ে চেক
n_groups_exact, min_size_exact, violations_exact = k_anonymity_report(
    records, ["age", "zip", "gender"], k=3
)
print("=== এক্সাক্ট কোয়াসি-আইডেন্টিফায়ার (age, zip, gender) ===")
print(f"মোট গ্রুপ সংখ্যা:      {n_groups_exact}")
print(f"সর্বনিম্ন গ্রুপ সাইজ:   {min_size_exact}")
print(f"k=3 লঙ্ঘনকারী রেকর্ড: {len(violations_exact)} / {len(records)}  -> {violations_exact}")

# ধাপ ২: বয়স ও জিপ কোড জেনারেলাইজ করে নতুন ফিল্ড যোগ করা
for r in records:
    r["age_bracket"] = age_bracket(r["age"])
    r["zip3"] = zip_prefix(r["zip"])

n_groups_gen, min_size_gen, violations_gen = k_anonymity_report(
    records, ["age_bracket", "zip3", "gender"], k=3
)
print("\n=== জেনারেলাইজড কোয়াসি-আইডেন্টিফায়ার (age_bracket, zip3, gender) ===")
print(f"মোট গ্রুপ সংখ্যা:      {n_groups_gen}")
print(f"সর্বনিম্ন গ্রুপ সাইজ:   {min_size_gen}")
print(f"k=3 লঙ্ঘনকারী রেকর্ড: {len(violations_gen)} / {len(records)}  -> {violations_gen}")

print(f"\nজেনারেলাইজেশনের প্রভাব: সর্বনিম্ন গ্রুপ সাইজ {min_size_exact} থেকে বেড়ে {min_size_gen} হয়েছে "
      f"({n_groups_exact} -> {n_groups_gen} গ্রুপ), লঙ্ঘন {len(violations_exact)} থেকে কমে {len(violations_gen)} হয়েছে।")

    
এক্সাক্ট কোয়াসি-আইডেন্টিফায়ার দিয়ে প্রতিটি রেকর্ডের বয়স আলাদা হওয়ায় (৩২, ৩৫, ৩৮, ... — সবই অনন্য) প্রতিটি গ্রুপের সাইজ ঠিক ১ — অর্থাৎ ১২টি রেকর্ডই k=3 লঙ্ঘন করে। বয়সকে দশকের ব্র্যাকেটে এবং জিপ কোডকে প্রথম ৩ ডিজিটে জেনারেলাইজ করার পর একই ১২টি রেকর্ড মাত্র ৪টি গ্রুপে ভাগ হয়ে যায়, প্রতিটি গ্রুপের সাইজ ঠিক ৩ — সর্বনিম্ন গ্রুপ সাইজ ১ থেকে ৩-এ উঠে যাওয়ায় ডেটাসেটটি এখন সম্পূর্ণরূপে k=3-অ্যানোনিমাস।
মূল কথা · Key takeaway

k-anonymity একটি নির্দিষ্ট, পরিমাপযোগ্য গ্যারান্টি দেয় — কিন্তু এটি বিনামূল্যে আসে না। জেনারেলাইজেশন যত বেশি (বয়স ব্র্যাকেট যত প্রশস্ত, জিপ প্রিফিক্স যত ছোট), ডেটাসেট তত বেশি নিরাপদ কিন্তু তত কম নির্ভুল/উপযোগী — এটি একটি প্রাইভেসি-বনাম-উপযোগিতা (utility) ট্রেড-অফ, যা L13-এর ডিফারেনশিয়াল প্রাইভেসিতেও ফিরে আসবে। এছাড়া k-anonymity নিজে থেকে সব আক্রমণ ঠেকায় না (যেমন একটি গ্রুপের সবার একই সংবেদনশীল রোগ থাকলে, গ্রুপে থাকা মাত্রই সেই তথ্য ফাঁস হয়ে যায়) — এটি একটি শক্তিশালী প্রথম ধাপ, চূড়ান্ত সমাধান নয়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের কোডে ডায়াগনসিসের মতো কোনো সংবেদনশীল ফিল্ড নেই, শুধু কোয়াসি-আইডেন্টিফায়ার আছে — তাহলে k-anonymity আসলে ঠিক কী রক্ষা করছে?

এটি রক্ষা করছে একজন ব্যক্তির রেকর্ড শনাক্তযোগ্যতা — অর্থাৎ কেউ যদি জানে "একজন ৩২ বছর বয়সী পুরুষ, জিপ ১০০০১-এ থাকেন" এমন কারো ডেটা এই ডেটাসেটে আছে, এক্সাক্ট কোয়াসি-আইডেন্টিফায়ারে তিনি সরাসরি R1 রেকর্ডটি শনাক্ত করে ফেলতে পারবেন এবং তার সাথে জোড়া লাগানো যেকোনো সংবেদনশীল ফিল্ড (রোগ, আয়, ইত্যাদি) পড়তে পারবেন। জেনারেলাইজড ভার্সনে তিনি শুধু বলতে পারবেন রেকর্ডটি ৩ জনের একটি গ্রুপের মধ্যে কোথাও আছে — কিন্তু ঠিক কোনটি, তা নিশ্চিতভাবে বলতে পারবেন না।

প্র ০২ যদি একটি গ্রুপের ৩ জনের সবারই ঠিক একই রোগ নির্ণয় (diagnosis) থাকে, তাহলে সেই গ্রুপ k=3-অ্যানোনিমাস হওয়া সত্ত্বেও কেন প্রাইভেসি সুরক্ষা ব্যর্থ হয়?

কারণ k-anonymity শুধু নিশ্চিত করে যে কোন নির্দিষ্ট ব্যক্তি কোন রেকর্ড তা বলা কঠিন — কিন্তু যদি গ্রুপের সবার সংবেদনশীল মান একই হয়, তাহলে আক্রমণকারীর কোন নির্দিষ্ট রেকর্ড শনাক্ত করার প্রয়োজনই নেই — তিনি শুধু জানেন ব্যক্তিটি ওই গ্রুপে আছেন, আর গ্রুপের সবার রোগ একই হওয়ায় সেই রোগটিই নিশ্চিতভাবে জেনে যান। এই দুর্বলতাকে "হোমোজেনিটি অ্যাটাক" বলা হয়, আর এটিই কেন আরও শক্তিশালী মডেল (যেমন l-diversity) তৈরি হয়েছিল।

প্র ০৩ যদি আমরা zip3-এর বদলে আরও কম নির্দিষ্ট zip1 (প্রথম ১ ডিজিট) ব্যবহার করতাম, সর্বনিম্ন গ্রুপ সাইজের কী হতো — বাড়তো নাকি কমতো?

বাড়তো (বা অন্তত কমতো না) — কারণ কম নির্দিষ্ট জিপ প্রিফিক্স মানে বেশি রেকর্ড একই মান শেয়ার করবে, ফলে গ্রুপ আরও বড় হবে। এটিই জেনারেলাইজেশনের সাধারণ নিয়ম — কোয়াসি-আইডেন্টিফায়ার যত কম নির্দিষ্ট (coarser), গ্রুপ সাইজ তত বড়, প্রাইভেসি তত শক্তিশালী — কিন্তু ডেটার নির্ভুলতা/উপযোগিতা তত কমে যায় (এই ক্ষেত্রে zip1 দিয়ে একটি পুরো অঞ্চলের সবাইকে আলাদা করার ক্ষমতা হারিয়ে যাবে)।

অনুশীলন

  1. চিন্তা করুন: সরকারি বা গবেষণা প্রতিষ্ঠানগুলো যখন "অ্যানোনিমাইজড" স্বাস্থ্য বা শুমারি ডেটা সর্বজনীনভাবে প্রকাশ করে, তখন কেন শুধু নাম মুছে ফেলাই যথেষ্ট নয় তা নিজের ভাষায় ব্যাখ্যা করুন।

    কারণ বয়স, জিপ কোড, ভর্তির তারিখ, বা পেশার মতো ফিল্ড — যেগুলো "সংবেদনশীল" মনে না হলেও — একসাথে মিলে প্রায়ই একজন নির্দিষ্ট ব্যক্তিকে অনন্যভাবে চিহ্নিত করে দিতে পারে, বিশেষ করে যদি আক্রমণকারী একটি দ্বিতীয় সর্বজনীন ডেটাসেট (যেমন ভোটার তালিকা বা সোশ্যাল মিডিয়া প্রোফাইল) দিয়ে সেই কোয়াসি-আইডেন্টিফায়ার মিলিয়ে দেখতে পারেন — এটিই k-anonymity যাচাই করার আসল কারণ।

  2. পরীক্ষা করুন: উপরের কোড সেলে k_anonymity_report-কে k=4 দিয়ে জেনারেলাইজড ডেটাতে (age_bracket, zip3, gender) আবার চালান। এখন কতগুলো রেকর্ড লঙ্ঘন করবে?

    প্রতিটি গ্রুপের সাইজ ঠিক ৩ হওয়ায় (৪টি গ্রুপ, প্রতিটিতে ৩ জন), k=4 দিয়ে চেক করলে সবগুলো গ্রুপই 4-এর চেয়ে ছোট, তাই সবক'টি ১২টি রেকর্ডই লঙ্ঘনকারী হিসেবে চিহ্নিত হবে। এটি দেখায় k-anonymity একটি নির্দিষ্ট k-এর সাপেক্ষে সংজ্ঞায়িত — একই জেনারেলাইজেশন এক k-এর জন্য যথেষ্ট হলেও অন্য, বড় k-এর জন্য যথেষ্ট নাও হতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
কনসেন্ট, ডেটা মিনিমাইজেশন ও পারপাস লিমিটেশন