অধ্যায় 1 · ঝুঁকি ও ফ্রেমওয়ার্ক
ইমপ্যাক্ট অ্যাসেসমেন্ট: একটা সীমিত AI ফিচারের পরিধি, স্টেকহোল্ডার ও ক্ষতি
- পৃষ্ঠা 1 / 22
- 20 মিনিট পড়া
বিগিনার টিউটোরিয়ালের দৈনন্দিন AI নৈতিকতা: কারা উপকৃত হয় আর কাদের ক্ষতি হতে পারে পাতায় আপনি এই প্রশ্নগুলো মনে মনে বা একটা ওয়ার্কশিটে করেছিলেন। দলে কাজ করতে গেলে দরকার এমন কিছু যা বারবার ব্যবহার করা যায়: লিখিত একটা ইমপ্যাক্ট অ্যাসেসমেন্ট (impact assessment), যা দুজন মানুষ পড়তে, প্রশ্ন করতে আর হালনাগাদ করতে পারেন। এই পাতায় শেখানো হবে একটা ছোট, সীমিত AI ফিচারের জন্য এমন অ্যাসেসমেন্টের পরিধি ঠিক করা, আর ঝুঁকিগুলো এমন একটা রেজিস্টারে রাখা যা প্রোগ্রাম দিয়ে যাচাই করা যায়।
আপনার দরকার Python 3.12 এবং শুধু তার স্ট্যান্ডার্ড লাইব্রেরি: কোনো প্যাকেজ, নেটওয়ার্ক বা মডেল কল লাগবে না। ব্লকগুলো ক্রমে একটা সেশনে লিখুন; এগুলো ভেরিয়েবল ভাগ করে নেয়, আর প্রতিটা ব্লকের নিচের লেখাটা তার আসল আউটপুট।
এই টিউটোরিয়ালের সততার নিয়ম। প্রতিটা ডেটাসেট কৃত্রিম (synthetic) এবং
Toy data:লেবেল দেওয়া। প্রতিটা কোম্পানি, ব্যক্তি ও সংখ্যা কাল্পনিক। ফলাফল শুধু দেখায় একটা হিসাব কীভাবে কাজ করে; বাস্তব মানুষ বা বাস্তব সিস্টেম সম্পর্কে এটা কিছুই বলে না। এখানে কিছুই আইনি পরামর্শ নয়।
যা শিখবেন
- সিস্টেমের সীমানা ও উদ্দেশ্য কীভাবে ঠিক করবেন, এবং ব্যবহারকারী, প্রভাবিত অ-ব্যবহারকারী ও অন্য স্টেকহোল্ডারদের নাম কীভাবে লিখবেন।
- একটা AI ফিচারের ইনপুট, আউটপুট ও সিদ্ধান্ত-বিন্দু কীভাবে ম্যাপ করবেন, আর যে সিদ্ধান্ত কেউ নেয় না সেটা কীভাবে ধরবেন।
- সম্ভাবনা, তীব্রতা, প্রত্যাবর্তনযোগ্যতা, এক্সপোজার ও স্থায়িত্ব আলাদা রেখে সহজাত ও অবশিষ্ট ঝুঁকি কীভাবে লিখবেন।
- ব্যক্তিগত ও সমষ্টিগত ক্ষতির পার্থক্য, রিস্ক ম্যাট্রিক্স বানানো, আর সৎভাবে বলা যে এর স্কোর একটা বেছে নেওয়া পদ্ধতি।
- অনুমান ও প্রমাণের মান কীভাবে লিখবেন, আর কিছু বানিয়ে না দিয়ে Python-এ রেজিস্টার কীভাবে যাচাই করবেন।
এই টিউটোরিয়াল কীভাবে ব্যবহার করবেন
অ্যাডভান্সড টিউটোরিয়ালে ছয়টা অধ্যায় আছে: ঝুঁকি ও ফ্রেমওয়ার্ক থেকে শুরু করে ন্যায্যতা, ডকুমেন্টেশন, নিরাপত্তা, পরিমাপ এবং শেষে একটা ক্যাপস্টোন। প্রথম কয়েকটা পাতায় একটাই চলমান উদাহরণ থাকবে: একটা কাল্পনিক আবেদনকারী-বাছাই (applicant-screening) ফিচার।
কোর্সের সিদ্ধান্তের কাঠামো নিচে দেওয়া হলো। নৈতিক উদ্বেগ, আইনি বাধ্যবাধকতা ও কোম্পানির পলিসি তিনটা আলাদা জিনিস; লেখার সময় এগুলো আলাদা রাখুন। ব্লকের লেখা ইংরেজিতেই থাকে; ধাপগুলো হলো: ১ পরিস্থিতি, ২ কারা প্রভাবিত, ৩ উপকার ও ক্ষতি, ৪ বিকল্প (AI না ব্যবহার করাসহ), ৫ প্রমাণ ও অজানা, ৬ সিদ্ধান্ত ও শর্ত, ৭ জবাবদিহি কার, ৮ পর্যালোচনার তারিখ।
টেমপ্লেট:
1 Situation what is proposed, for what purpose, inside which boundary
2 Who is affected users, affected non-users, other stakeholders
3 Benefits and harms intended benefits, plausible misuse, harms (individual and collective)
4 Options including "do not use AI" and "use it for less"
5 Evidence and unknowns what we measured, what we assume, how good the evidence is
6 Decision, conditions approve, approve with conditions, or stop
7 Who is accountable a named role, not "the team"
8 Review date when and what triggers a new lookফ্রেমওয়ার্ক এই কাজকে একটা নাম দেয়। NIST AI RMF 1.0 (জানুয়ারি ২০২৩, স্বেচ্ছামূলক) এর চারটা ফাংশন: Govern, Map, Measure ও Manage। এই পাতা Map-এর সবচেয়ে কাছাকাছি (আমাদের ব্যাখ্যা: প্রেক্ষাপট ও ঝুঁকি বোঝা)। নীতি ও ফ্রেমওয়ার্ক বাস্তবে: মূল্যবোধ থেকে পরীক্ষাযোগ্য নিয়ন্ত্রণ পাতায় এ নিয়ে আবার আলোচনা হবে।
আগে পরিধি: সীমানা ও উদ্দেশ্য
সিস্টেমের সীমানা ও উদ্দেশ্য ঠিক করুন। সিস্টেমের সীমানা (system boundary) বলে আপনি কী মূল্যায়ন করছেন: ফিচারটা, সে যে ডেটা পড়ে, যাঁরা তার আউটপুট দেখেন এবং যে সিদ্ধান্তে সে প্রভাব ফেলে। উদ্দেশ্য (intended purpose) হলো এক বাক্য, যা বলে ফিচারটা কিসের জন্য। বাকি সবকিছু পরিধির বাইরে; সেটাও লিখে রাখুন, কারণ ফিচার প্রায়ই এমন কাজে ব্যবহার হয় যার জন্য সেটা বানানো হয়নি।
কাল্পনিক পরিস্থিতি:
Company: Harbourline Staffing, a small recruitment agency (invented).
Feature: ShortlistHelper reads the CVs sent for one job and gives each a "fit score" from 0 to 100.
Today: two recruiters read about 400 CVs per job by hand. The agency receives about 6000
applications a year (Toy data) and wants to save reading time.
Plan: the recruiter sees the CVs in score order and decides whom to interview.
Question: before anyone approves a pilot, what could go wrong, for whom, and how bad is it?কার্ডের সারকথা: হারবারলাইন স্টাফিং (কাল্পনিক) নামের একটা ছোট নিয়োগ-সংস্থা ShortlistHelper নামের ফিচার চায়, যা এক চাকরির জন্য আসা সিভিগুলোকে ০ থেকে ১০০ নম্বর দেয়। বছরে প্রায় ৬০০০টা আবেদন আসে (টয় ডেটা)। রিক্রুটার স্কোর অনুযায়ী সিভি দেখে ঠিক করেন কাকে ইন্টারভিউয়ে ডাকবেন। প্রশ্ন: পাইলট অনুমোদনের আগে কী ভুল হতে পারে, কার জন্য, আর কতটা খারাপ?
ভালো উদ্দেশ্য সংকীর্ণ: "এক চাকরির সিভিগুলো সাজাও, যাতে রিক্রুটার জানেন কোনটা আগে পড়বেন।" দুর্বল উদ্দেশ্য হলো "নিয়োগ উন্নত করা"। সংকীর্ণটা পরীক্ষা করা যায়, আর বাইরে কী আছে তাও দেখায়। কেউ পরে মানুষকে বাদ দেওয়া বা কর্মীদের র্যাংক করার প্রস্তাব দিলে সেটা নতুন ফিচার, নতুন অ্যাসেসমেন্ট।
কারা প্রভাবিত
সরাসরি ব্যবহারকারী, প্রভাবিত অ-ব্যবহারকারী ও স্টেকহোল্ডারদের চিহ্নিত করুন। ব্যবহারকারী ফিচারটা চালান। প্রভাবিত অ-ব্যবহারকারী কখনো ফিচারটা ছোঁন না, কিন্তু তাঁদের স্কোর হয়, র্যাংক হয়, নজরে রাখা হয় বা তাঁদের নিয়ে সিদ্ধান্ত হয়। স্টেকহোল্ডারের স্বার্থ বা দায়িত্ব আছে, কিন্তু তিনি এ দুইয়ের কোনোটাই নন। দ্বিতীয় দলকে ভুলে যাওয়া সহজ, আর আপত্তি জানানোর ক্ষমতা তাঁদেরই প্রায়ই সবচেয়ে কম।
| দল | উদাহরণে | কেন গুরুত্বপূর্ণ |
|---|---|---|
| সরাসরি ব্যবহারকারী | রিক্রুটার; যে হায়ারিং ম্যানেজার চাকরির চাহিদা লেখেন | তাঁরা র্যাংকিং দেখে কাজ করেন এবং তা বদলাতে পারেন |
| প্রভাবিত অ-ব্যবহারকারী | আবেদনকারী; বর্তমান কর্মী, যাঁদের সিভি বা স্কোর আবার ব্যবহার হতে পারে | নিচু র্যাংক তাঁদের সুযোগ বদলায়; কারণটা তাঁরা কখনো দেখেন না |
| অন্য স্টেকহোল্ডার | সংস্থার মালিক; ভেন্ডর; ক্লায়েন্ট নিয়োগকর্তা; স্থানীয় চাকরির বাজার; নিয়োগ নিয়ে কোনো নিয়ন্ত্রক থাকলে তিনি | খরচ, সুনাম বা আইনি দায়িত্ব তাঁদের ওপর পড়ে |
উপকার ও সম্ভাব্য অপব্যবহার
উদ্দিষ্ট উপকার ও সম্ভাব্য অপব্যবহার লিখুন। আগে উপকারগুলো সৎভাবে লিখুন: এখানে রিক্রুটাররা পড়তে কম সময় দেন, আর উত্তর হয়তো আগে যায়। কোনো উপকার তখনই গণ্য হবে যখন আপনি বলতে পারবেন কীভাবে তা টের পাবেন। তারপর লিখুন সম্ভাব্য অপব্যবহার: যে ব্যবহার উদ্দিষ্ট নয় কিন্তু সহজ ও সম্ভাব্য, যেমন কোনো ম্যানেজার বর্তমান কর্মীদের র্যাংক করতে স্কোর চাইলেন, বা কোনো আবেদনকারী সিভিতে চাকরির বিজ্ঞাপনের কিওয়ার্ড ভরে দিলেন। অপব্যবহার লেখা মানে কাউকে অভিযুক্ত করা নয়; এটা এমন একটা টুল সম্পর্কে পূর্বাভাস যা আবার ব্যবহার করা সহজ।
যুক্তিসংগত মানুষ এটা আলাদাভাবে ওজন করেন। এক চাকরির জন্য ৪০০টা সিভি পাওয়া রিক্রুটার হয়তো বাঁচানো সময়কে সবচেয়ে গুরুত্ব দেবেন; আবেদনকারী হয়তো চাইবেন একজন মানুষ প্রতিটা সিভি পড়ুক। অ্যাসেসমেন্ট এর মীমাংসা করে না; দুটোই লিখে রাখে এবং যিনি সিদ্ধান্ত নেবেন তাঁর সামনে ট্রেড-অফটা দৃশ্যমান করে।
ইনপুট, আউটপুট ও সিদ্ধান্ত-বিন্দু ম্যাপ করা
AI ফিচারের ইনপুট, আউটপুট ও সিদ্ধান্ত-বিন্দু ম্যাপ করুন। প্রবাহটাকে ডেটা হিসেবে আঁকুন। কী ঢোকে, কী বের হয়, আর কোথায় কোথায় কিছু ঠিক করা হয় এবং কে ঠিক করে, তা তালিকা করুন। ছোট একটা কাঠামোই যথেষ্ট, আর প্রোগ্রাম তা যাচাই করতে পারে।
টয় ডেটা: বাছাই-উদাহরণের ফিচার ম্যাপ।
FEATURE = {
"name": "ShortlistHelper (hypothetical)",
"purpose": "order the CVs for one job so a recruiter knows which to read first",
"out_of_scope": ["rejecting anyone", "ranking current staff", "pay or promotion"],
"flow": [ # (step, what happens, who decides)
("1 input", "applicant uploads a CV", "applicant"),
("2 input", "hiring manager lists job requirements", "hiring manager"),
("3 AI", "feature gives each CV a fit score 0-100", "model"),
("4 output", "CVs are shown as a ranked list", "feature"),
("5 decision", "recruiter opens the top 20% of CVs", "recruiter"),
("6 decision", "the other 80% are never opened", "nobody (default)"),
("7 decision", "recruiter invites people to interview", "recruiter"),
],
}
print(FEATURE["name"], "-", FEATURE["purpose"])
print("out of scope:", ", ".join(FEATURE["out_of_scope"]))
for step, what, who in FEATURE["flow"]:
print(f"{step:<11}{what:<44}{who}")
silent = [s for s, _, who in FEATURE["flow"] if who.startswith("nobody")]
print("steps where no person decides:", silent)ShortlistHelper (hypothetical) - order the CVs for one job so a recruiter knows which to read first
out of scope: rejecting anyone, ranking current staff, pay or promotion
1 input applicant uploads a CV applicant
2 input hiring manager lists job requirements hiring manager
3 AI feature gives each CV a fit score 0-100 model
4 output CVs are shown as a ranked list feature
5 decision recruiter opens the top 20% of CVs recruiter
6 decision the other 80% are never opened nobody (default)
7 decision recruiter invites people to interview recruiter
steps where no person decides: ['6 decision']৫ ও ৭ নম্বর ধাপ একজন মানুষের সিদ্ধান্ত। ৬ নম্বর ধাপও একটা সিদ্ধান্ত, যদিও কেউ তা নেয় না: মডেল যেখানে বসিয়েছে তার কারণে ৮০% আবেদনকারীর সিভি কখনো পড়াই হয় না। এখানেই একটা AI স্কোর মানুষের জীবনের ফলাফল হয়ে ওঠে। বিগিনার টিউটোরিয়ালের অটোমেশন বায়াসের ধারণা (র্যাংকিং কর্তৃত্বপূর্ণ দেখায় বলে তাতে ভরসা করা) এখানেই কাজ করে।
একটা ঝুঁকি, একটা রেকর্ড
রিস্ক রেজিস্টার হলো রিস্ক রেকর্ডের তালিকা। নিচের ফিল্ডগুলো এই পাতার ধারণাগুলো বহন করে; কোডের পরের অংশগুলো প্রতিটা ব্যাখ্যা করে।
| ফিল্ড | কী লেখে |
|---|---|
likelihood, severity | সহজাত ঝুঁকি: কোনো নিয়ন্ত্রণের আগে, প্রতিটা আপনার দলের ঠিক করা ১-৫ স্কেলে |
reversibility | ক্ষতি ফেরানো যায় কি না: easy, hard, irreversible। আলাদা করে লেখা হয়, গুণফলের ভেতরে লুকানো হয় না |
exposed, duration | বছরে কতজন মানুষ, আর কতদিন ধরে (one-off, weeks, persistent) |
level | ব্যক্তিগত ক্ষতি না সমষ্টিগত ক্ষতি |
controls, res_likelihood, res_severity | বিদ্যমান ব্যবস্থা এবং তার পরের অবশিষ্ট ঝুঁকি |
assumptions, evidence | আপনি কী ধরে নিয়েছিলেন আর সমর্থন কতটা ভালো |
এই ফাইলটা তৈরি করুন। এটা শুধু স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করে, আর ভ্যালিডেটর যা নেই তা জানায়; অনুমান করে ফাঁক ভরে না।
# file: risk.py
from dataclasses import dataclass, fields
LEVELS = ("individual", "collective")
REVERSIBILITY = ("easy", "hard", "irreversible")
DURATION = ("one-off", "weeks", "persistent")
EVIDENCE = ("none", "opinion", "internal-test", "independent")
BANDS = ((16, "very high"), (10, "high"), (5, "medium"), (1, "low")) # our chosen cut-offs
OPTIONAL = ("controls", "res_likelihood", "res_severity")
@dataclass
class Risk:
id: str
title: str = ""
level: str = "" # individual or collective harm
who: str = ""
likelihood: int = 0 # 1-5, inherent: before any control
severity: int = 0 # 1-5, inherent
reversibility: str = "" # recorded separately, not multiplied in
exposed: int = 0 # people per year
duration: str = ""
controls: tuple = ()
res_likelihood: int = 0 # residual: after the controls above; 0 = not recorded
res_severity: int = 0
assumptions: tuple = ()
evidence: str = ""
def residual(self):
if not self.controls:
return self.likelihood, self.severity
return self.res_likelihood, self.res_severity
def score(likelihood, severity):
return likelihood * severity
def band(points):
return next(name for floor, name in BANDS if points >= floor)
def problems(r):
"""Missing or invalid fields. Nothing is guessed or filled in."""
found = [f"{f.name} is missing" for f in fields(r)
if f.name not in OPTIONAL and not getattr(r, f.name)]
for name, allowed in (("level", LEVELS), ("reversibility", REVERSIBILITY),
("duration", DURATION), ("evidence", EVIDENCE)):
value = getattr(r, name)
if value and value not in allowed:
found.append(f"{name} '{value}' is not one of {allowed}")
for name in ("likelihood", "severity", "res_likelihood", "res_severity"):
value = getattr(r, name)
if value and not 1 <= value <= 5:
found.append(f"{name} {value} is outside 1-5")
if r.controls and not (r.res_likelihood and r.res_severity):
found.append("controls listed but residual scores missing")
if not r.controls and (r.res_likelihood or r.res_severity):
found.append("residual scores given but no controls listed")
if all(r.residual()) and score(*r.residual()) > score(r.likelihood, r.severity):
found.append("residual is higher than inherent: check")
return found
def caveats(r):
"""Valid records can still be weak."""
notes = []
if r.evidence == "none":
notes.append("scores rest on assumption only")
if r.evidence == "opinion" and r.severity >= 4:
notes.append("severe risk with opinion-only evidence")
return notes
def table(risks):
rows = sorted(risks, key=lambda r: (-score(r.likelihood, r.severity), -r.severity, r.id))
out = [f"{'id':<4}{'level':<12}{'L':>2}{'S':>3} {'rev':<13}{'exposed':>8} "
f"{'duration':<11}{'pts':>3} {'band':<10}evidence"]
for r in rows:
pts = score(r.likelihood, r.severity)
out.append(f"{r.id:<4}{r.level:<12}{r.likelihood:>2}{r.severity:>3} {r.reversibility:<13}"
f"{r.exposed:>8} {r.duration:<11}{pts:>3} {band(pts):<10}{r.evidence}")
return "\n".join(out)
def matrix(risks, residual=False):
cell = {}
for r in risks:
key = r.residual() if residual else (r.likelihood, r.severity)
cell.setdefault(key, []).append(r.id)
lines = ["sev\\lik" + "".join(f"{n:^9}" for n in range(1, 6))]
for s in range(5, 0, -1):
lines.append(f"{s:^7}" + "".join(f"{','.join(cell.get((n, s), ['.'])):^9}" for n in range(1, 6)))
return "\n".join(lines)ব্যান্ডের কাট-অফগুলো (১৬, ১০, ৫) আমাদের বেছে নেওয়া। এগুলো একটা কনস্ট্যান্টে আছে, যাতে দল বদলাতে পারে এবং কারণ লিখে রাখতে পারে।
সহজাত ও অবশিষ্ট ঝুঁকি
সহজাত ঝুঁকি (inherent) আর অবশিষ্ট ঝুঁকির (residual) পার্থক্য করুন। সহজাত ঝুঁকি হলো নিয়ন্ত্রণ ছাড়া ঝুঁকি। অবশিষ্ট ঝুঁকি হলো আপনার আসলে যে নিয়ন্ত্রণগুলো আছে তার পর যা থাকে, যেগুলোর পরিকল্পনা আছে তার পর নয়। দুটো লিখলে বোঝা যায় নিয়ন্ত্রণগুলো কী কিনে দিচ্ছে। কোডে নিয়ন্ত্রণহীন ঝুঁকির অবশিষ্ট স্কোর সহজাতের সমান ধরা হয়; নিয়ন্ত্রণ থাকলে নতুন স্কোর লিখতেই হয়, আর অবশিষ্ট স্কোর সহজাতের চেয়ে বেশি হলে সেটাকে সম্ভাব্য ভুল হিসেবে চিহ্নিত করা হয়।
সম্ভাবনা, তীব্রতা ও প্রত্যাবর্তনযোগ্যতা
সম্ভাবনা, তীব্রতা ও প্রত্যাবর্তনযোগ্যতা অনুমান করুন তিনটা আলাদা উত্তর হিসেবে। প্রতিটা স্কেলের অ্যাংকর (anchor) লিখে রাখতে হয়, নইলে দুজন মানুষ একই সংখ্যা দিয়ে আলাদা জিনিস বোঝাবেন।
| স্কোর | সম্ভাবনা (এক বছরের ব্যবহারে) | তীব্রতা (একজন মানুষের জন্য) |
|---|---|---|
| ১ | দলকে অবাক করবে | সামান্য বিরক্তি, সঙ্গে সঙ্গে ঠিক হয় |
| ৩ | বছরে কোনো এক সময় ঘটতে পারে | সুযোগ বা টাকা হারানো, মেরামতে চেষ্টা লাগে |
| ৫ | প্রায় প্রতি মাসেই ঘটার কথা | জীবিকা, স্বাস্থ্য বা অধিকারের গুরুতর ও দীর্ঘস্থায়ী ক্ষতি |
প্রত্যাবর্তনযোগ্যতা আলাদা প্রশ্ন করে: ঘটে গেলে সেটা ঠিক করা যায় কি না? ভুলে দেরি হওয়া উত্তর easy। কেউ টের পায়নি এমন বাতিল hard, কারণ যা ঘটেছে বলে কেউ জানে না তা চাওয়া যায় না। ফাঁস হয়ে যাওয়া সিভি-ডেটা irreversible।
এক্সপোজার, মাত্রা ও স্থায়িত্ব
সম্ভাব্য ক্ষতির এক্সপোজার, মাত্রা ও স্থায়িত্ব চিহ্নিত করুন। এক্সপোজার (exposure) হলো কতজন মানুষ প্রভাবিত হতে পারেন, মাত্রা (scale) হলো প্রত্যেকের ওপর প্রভাব কত বড়, আর স্থায়িত্ব (duration) হলো কতদিন চলে। সবার ওপর ছোট ক্ষতি কয়েকজনের বড় ক্ষতির চেয়ে ভারী হতে পারে। রেজিস্টারে বছরে কতজন এক্সপোজড তা রাখা হয়; নিচের টেবিলে সেটা স্থায়িত্বের পাশে দেখা যাবে। ৬ নম্বর ধাপের কাট-অফের কারণে R2 প্রভাবিত করে ৬০০০ আবেদনকারীর মধ্যে ৪৮০০ জনকে (৮০%); R5 প্রভাবিত করে ২৫০ জনকে (প্রায় ৪%)। ৪৮০০ জনের ওপর স্থায়ী ক্ষতি আর ৬০০ জনের ওপর কয়েক সপ্তাহের ক্ষতি পুরোপুরি আলাদা, যদিও একজনের অভিজ্ঞতা দেখতে একই লাগতে পারে।
ব্যক্তিগত ও সমষ্টিগত ক্ষতি
ব্যক্তিগত ক্ষতি আর সমষ্টিগত বা সামাজিক ক্ষতির পার্থক্য করুন। ব্যক্তিগত ক্ষতি পড়ে চেনা মানুষের ওপর: একটা সিভির স্কোর কমে যাওয়া, ডেটা বেশিদিন রেখে দেওয়া। সমষ্টিগত ক্ষতি অনেক মানুষের মধ্যে দেখা দেয় এবং কোনো একটা কেসে অদৃশ্য থাকতে পারে: শর্টলিস্ট যদি আগের নিয়োগপ্রাপ্তদের প্রোফাইলের দিকে সরে যায়, কোনো একজন আবেদনকারী তা প্রমাণ করতে পারেন না, অথচ কয়েক বছরে ইন্টারভিউ পাওয়া মানুষের পরিধি সরু হয়ে যায়। প্রতিটা ঝুঁকির level লিখুন, যাতে সমষ্টিগত ঝুঁকি হারিয়ে না যায়। এদের প্রমাণও আলাদা: একটা অভিযোগ ব্যক্তিগত ক্ষতি দেখায়; সমষ্টিগত ক্ষতি দেখাতে হলে অনেক কেস তুলনা করতে হয়।
রেজিস্টার ভরুন ও যাচাই করুন
এবার স্ক্রিনিং ফিচারের কাল্পনিক ঝুঁকিগুলো রেকর্ডে বসান: ছয়টা সম্পূর্ণ রেকর্ড, আর একটা অসম্পূর্ণ রেকর্ড R7, যাতে অসম্ভব একটা স্কোর ও বানানো একটা প্রমাণ-লেবেল আছে। চেকার R7-এর ফাঁকগুলো জানাবে, আর কিছু নয়।
টয় ডেটা: সাতটা কাল্পনিক ঝুঁকি ও তাদের যাচাই।
from risk import Risk, problems, caveats
RAW = [ # Toy data: invented risks for the hypothetical screening feature, 6000 applicants a year
dict(id="R1", title="Score penalises career breaks", level="individual",
who="applicants with a career break", likelihood=3, severity=4, reversibility="hard",
exposed=900, duration="persistent", controls=("remove gap feature", "quarterly group check"),
res_likelihood=2, res_severity=3, evidence="opinion",
assumptions=("15% of applicants have a break (guess, not measured)",)),
dict(id="R2", title="Recruiters trust the ranking", level="individual",
who="applicants ranked below the cut", likelihood=4, severity=3, reversibility="hard",
exposed=4800, duration="persistent", controls=("sample 50 low-ranked CVs a month",),
res_likelihood=3, res_severity=3, evidence="internal-test",
assumptions=("recruiters open only the top 20% of the list",)),
dict(id="R3", title="Unusual CV layouts parse badly", level="individual",
who="applicants with non-standard CVs", likelihood=3, severity=3, reversibility="easy",
exposed=600, duration="weeks", controls=("show a parse warning", "manual check path"),
res_likelihood=3, res_severity=2, evidence="internal-test",
assumptions=("10% of CVs do not follow the template (guess)",)),
dict(id="R4", title="CV text kept beyond hiring", level="individual",
who="all applicants", likelihood=2, severity=4, reversibility="irreversible",
exposed=6000, duration="persistent", evidence="opinion",
assumptions=("vendor keeps text for 90 days (contract not yet read)",)),
dict(id="R5", title="Score reused to rank staff", level="individual",
who="current employees", likelihood=2, severity=3, reversibility="hard",
exposed=250, duration="persistent", controls=("written purpose limit", "access log"),
res_likelihood=1, res_severity=3, evidence="none",
assumptions=("nobody has asked for this yet",)),
dict(id="R6", title="Shortlists drift toward past hires", level="collective",
who="future applicants, local job market", likelihood=3, severity=3, reversibility="hard",
exposed=6000, duration="persistent", controls=("yearly shortlist vs pool comparison",),
res_likelihood=3, res_severity=2, evidence="opinion",
assumptions=("past hires are not a balanced sample (guess)",)),
dict(id="R7", title="Appeal route unclear", likelihood=3, severity=7, evidence="rumour"),
]
register = [Risk(**d) for d in RAW]
for r in register:
issues = problems(r)
print(r.id, "INCOMPLETE" if issues else "complete")
for line in issues:
print(" problem:", line)
for line in caveats(r):
print(" caveat: ", line)
valid = [r for r in register if not problems(r)]R1 complete
caveat: severe risk with opinion-only evidence
R2 complete
R3 complete
R4 complete
caveat: severe risk with opinion-only evidence
R5 complete
caveat: scores rest on assumption only
R6 complete
R7 INCOMPLETE
problem: level is missing
problem: who is missing
problem: reversibility is missing
problem: exposed is missing
problem: duration is missing
problem: assumptions is missing
problem: evidence 'rumour' is not one of ('none', 'opinion', 'internal-test', 'independent')
problem: severity 7 is outside 1-5R7-কে স্কোর করা হয়নি, মেরামতও করা হয়নি: কারণসহ তাকে ফিরিয়ে দেওয়া হয়েছে। যেকোনো ডকুমেন্টেশন-চেকারের নিয়ম এটাই: যা নেই তা জানান, প্রমাণ বানাবেন না। যে প্রোগ্রাম ফাঁক ভরে দেয়, সে এমন রেজিস্টার বানায় যা দেখতে সম্পূর্ণ কিন্তু অর্থহীন। কেভিয়াট (caveat) দ্বিতীয় ধরনের বার্তা: R1 ও R4 গুরুতর ঝুঁকি, কিন্তু শুধু মতামতের ওপর দাঁড়িয়ে আছে, আর R5 শুধু অনুমানের ওপর।
রিস্ক ম্যাট্রিক্স, আর তা যা নয়
স্কোরকে বস্তুনিষ্ঠ সত্য না ধরে রিস্ক ম্যাট্রিক্স বানান। ম্যাট্রিক্সে এক অক্ষে সম্ভাবনা, অন্য অক্ষে তীব্রতা থাকে, আর প্রতিটা ঝুঁকি একটা ঘরে বসে।
from risk import table, matrix
print(f"{len(valid)} of {len(register)} records complete; R7 stays out until it is fixed\n")
print(table(valid))
print()
print(matrix(valid))6 of 7 records complete; R7 stays out until it is fixed
id level L S rev exposed duration pts band evidence
R1 individual 3 4 hard 900 persistent 12 high opinion
R2 individual 4 3 hard 4800 persistent 12 high internal-test
R3 individual 3 3 easy 600 weeks 9 medium internal-test
R6 collective 3 3 hard 6000 persistent 9 medium opinion
R4 individual 2 4 irreversible 6000 persistent 8 medium opinion
R5 individual 2 3 hard 250 persistent 6 medium none
sev\lik 1 2 3 4 5
5 . . . . .
4 . R4 R1 . .
3 . R5 R3,R6 R2 .
2 . . . . .
1 . . . . .R1 ও R2 দুটোরই স্কোর ১২, কিন্তু ঘর আলাদা: টেবিল টাই ভাঙে তীব্রতা দিয়ে, যা আমাদের বেছে নেওয়া নিয়ম। এবার দেখুন নিয়ন্ত্রণগুলো কী করল।
from risk import score
print("id inherent residual controls")
for r in valid:
print(f"{r.id:<4}{score(r.likelihood, r.severity):>7}{score(*r.residual()):>10} {len(r.controls)}")
print()
print("residual matrix")
print(matrix(valid, residual=True))id inherent residual controls
R1 12 6 2
R2 12 9 1
R3 9 6 2
R4 8 8 0
R5 6 3 2
R6 9 6 1
residual matrix
sev\lik 1 2 3 4 5
5 . . . . .
4 . R4 . . .
3 R5 R1 R2 . .
2 . . R3,R6 . .
1 . . . . .R4-এর কোনো নিয়ন্ত্রণ ছিল না, তাই সে নড়েনি; এখন তীব্রতা ৪-এর একমাত্র ঝুঁকি সে-ই। মোট সংখ্যার টেবিল এটা লুকিয়ে রাখে।
সতর্কতা। এই স্কোরগুলো একটা বেছে নেওয়া মূল্যায়ন-পদ্ধতি, মাপ নয়। স্কেল, গুণ করা আর ব্যান্ড মানুষের নেওয়া সিদ্ধান্ত, আর অন্য যুক্তিসংগত পদ্ধতি অন্য ক্রম দেবে। সংখ্যাগুলো আলোচনা ও সামঞ্জস্যে সাহায্য করে; এগুলো সত্য নয়, আর কোনো একটা গুরুতর কেসের বিচার-বিবেচনার জায়গা এগুলো নেবে না।
এটা দেখতে একই ছয় ঝুঁকিকে তিনভাবে সাজান: সম্ভাবনা ও তীব্রতার গুণফল; আগে তীব্রতা; এবং গুণফল, যেখানে ক্ষতি ফেরানো কঠিন হলে তীব্রতা এক বাড়ানো হয়।
from risk import score
STEP = {"easy": 0, "hard": 1, "irreversible": 1} # a chosen rule: raise severity by one if hard to undo
METHODS = {
"likelihood x severity": lambda r: score(r.likelihood, r.severity),
"severity first": lambda r: (r.severity, r.likelihood),
"L x S, severity +1 if hard to undo": lambda r: score(r.likelihood, min(5, r.severity + STEP[r.reversibility])),
}
for name, key in METHODS.items():
order = sorted(sorted(valid, key=lambda r: r.id), key=key, reverse=True) # ties keep id order
print(f"{name:<36}", " ".join(r.id for r in order))likelihood x severity R1 R2 R3 R6 R4 R5
severity first R1 R4 R2 R3 R6 R5
L x S, severity +1 if hard to undo R2 R1 R6 R4 R3 R5R4 (ফাঁস বা বেশিদিন রাখা সিভি-ডেটা) প্রথম নিয়মে পঞ্চম, দ্বিতীয় নিয়মে দ্বিতীয়। প্রত্যাবর্তনযোগ্যতা ধরলে R6 (সমষ্টিগত সরে যাওয়া) ওপরে ওঠে। এর কোনো ক্রমই ভুল নয়। দলের উচিত ফল দেখার আগে একটা নিয়ম বেছে নেওয়া, কারণ লিখে রাখা, আর যে ঝুঁকিগুলো নড়ে তাদের দেখা।
অনুমান ও প্রমাণের মান
অনুমান ও প্রমাণের মান নথিভুক্ত করুন। প্রতিটা স্কোর কিছু অনুমানের ওপর নির্ভর করে, যেমন "১৫% আবেদনকারীর কর্মবিরতি আছে (অনুমান, মাপা হয়নি)"। অনুমানগুলো স্কোরের পাশে লিখুন, আর যেটা অনুমান সেখানে "guess" লেবেল দিন। evidence ফিল্ড বলে সমর্থন কতটা ভালো: none, opinion, internal-test (দল নিজের ডেটায় কিছু মেপেছে) অথবা independent (দলের বাইরের কারও পর্যালোচনা)। দুর্বল প্রমাণ মানে কম স্কোর নয়: এর মানে স্কোর অনিশ্চিত, আর দুর্বল প্রমাণসহ গুরুতর ঝুঁকি মানে সিদ্ধান্তের আগে আরও জানার কারণ।
কাজের জায়গায় এটা কোথায় দেখা যায়
প্রোডাক্ট ও ঝুঁকি দলগুলো প্রতিটা AI ফিচারের জন্য একটা রেজিস্টার রাখে, পাইলটের আগে পর্যালোচনা করে, আর পরিবর্তনের পর হালনাগাদ করে। একটা কাজের অভ্যাস: কেউ "ঝুঁকি কম" বললে জিজ্ঞেস করুন "কোন রেকর্ড, কোন অনুমান, কোন প্রমাণ?"
সাধারণ ভুল
- "মডেল" বলে সীমানা টানা। ক্ষতি ঘটে মডেলের চারপাশের কর্মপ্রবাহে, যেমন না-পড়া ৮০%। ফিচারটাকে তার মানুষ ও সিদ্ধান্তসহ মূল্যায়ন করুন।
- প্রভাবিত অ-ব্যবহারকারীদের ভুলে যাওয়া। শুধু ব্যবহারকারীদের লিখলে যাঁদের হারানোর সম্ভাবনা সবচেয়ে বেশি তাঁরা বাদ পড়েন। জিজ্ঞেস করুন, কাদের নিয়ে সিদ্ধান্ত হচ্ছে অথচ তাঁদের জিজ্ঞেস করা হয়নি।
- স্কোরকে মাপ ভাবা। দুজন সৎভাবে স্কোর করেও আলাদা হতে পারেন। স্কেল, পদ্ধতি আর কারা একমত হয়েছেন তা লিখে রাখুন।
- গুণফলে প্রত্যাবর্তনযোগ্যতা হারিয়ে ফেলা। ফেরানো কঠিন ক্ষতি গুণফলে ছোট দেখাতে পারে। এটাকে আলাদা ফিল্ডে রাখুন এবং আলাদা করে দেখুন।
- রেজিস্টার সম্পূর্ণ দেখাতে ফাঁক ভরা। সৎ "missing" বা "evidence: none" বানানো সংখ্যার চেয়ে বেশি কাজের।
নিজে চেষ্টা করুন
এই পাতার নেমস্পেস ব্যবহার করুন। অনুশীলনগুলো বিগিনার টিউটোরিয়ালের বায়াস কোথা থেকে আসে: ডেটা, ডিজাইন, প্রক্সি ও ফিডব্যাক লুপ পাতাটা আবার ছোঁয়।
- সহজ। কোড না চালিয়ে ম্যাট্রিক্সে সম্ভাবনা ৪ ও তীব্রতা ৫-এর একটা নতুন ঝুঁকি বসান। তার পয়েন্ট ও ব্যান্ড কত? তারপর এমন একটা উপায়ের নাম বলুন যাতে একটা "ফিট স্কোর" কোনো দলের প্রতি অন্যায্য আচরণ করতে পারে (যেমন প্রক্সি ভেরিয়েবলের মাধ্যমে), এবং রেজিস্টারের কোন ফিল্ড তা লিখে রাখবে।
- মাঝারি। R7-কে ফিরিয়ে দেওয়া হয়েছিল। সংস্থার কোনো লিখিত আপিল-পথ না থাকলে ধরে সেটা সম্পূর্ণ করুন, প্রমাণ না বানিয়ে, এবং তাতে চেকার চালান।
- কঠিন। এই পাতার
METHODSডিকশনারি ব্যবহার করুন। এমন কোড লিখুন যা পদ্ধতিগুলোর মধ্যে যে ঝুঁকির র্যাংক দুই বা তার বেশি ঘর বদলায় তাদের র্যাংকসহ ছাপে।
উত্তর
সহজ। ৪ x ৫ = ২০ পয়েন্ট, তাই "very high" (১৬ বা বেশি), ম্যাট্রিক্সের ওপরের সারিতে, চতুর্থ কলামে। প্রক্সি ভেরিয়েবল (যেমন কর্মবিরতি, যা বয়স বা পরিচর্যার দায়িত্বের বদলে দাঁড়াতে পারে) একটা ভালো উদাহরণ: এটা লেখা হবে who (প্রভাবিত দল), assumptions (ফিচারটা প্রক্সি হিসেবে কাজ করে এই দাবি) এবং level (ব্যক্তিগত না সমষ্টিগত) ফিল্ডে।
পাস চেকলিস্ট: ২০ পয়েন্ট ও সঠিক ব্যান্ড; একটা নির্দিষ্ট কার্যপ্রণালীর নাম এবং একটা ফিল্ডের সঙ্গে তার সংযোগ; উল্লেখ যে উত্তরটা দলের নিজের স্কেলের ওপর নির্ভর করে। দুর্বল উত্তর শুধু স্কোর দেখে ন্যায্যতা নিয়ে রায় দেয়।
মাঝারি।
r7 = Risk(id="R7", title="Appeal route unclear", level="individual", who="rejected applicants",
likelihood=3, severity=3, reversibility="hard", exposed=4800, duration="persistent",
assumptions=("no appeal route is written down yet",), evidence="none")
print(problems(r7), caveats(r7))[] ['scores rest on assumption only']পাস চেকলিস্ট: প্রতিটা বাধ্যতামূলক ফিল্ড এমন কিছু দিয়ে ভরা যা পরিস্থিতি সমর্থন করে; evidence "none", কারণ কিছু মাপা হয়নি; চেকার কোনো সমস্যা দেয় না, একটা কেভিয়াট দেয়। দুর্বল উত্তর কেভিয়াট এড়াতে evidence-কে "internal-test" করে দেয়।
কঠিন।
ids = sorted(r.id for r in valid)
rank = {r_id: [] for r_id in ids}
for key in METHODS.values():
order = sorted(sorted(valid, key=lambda r: r.id), key=key, reverse=True)
for position, r in enumerate(order, 1):
rank[r.id].append(position)
for r_id in ids:
spread = max(rank[r_id]) - min(rank[r_id])
if spread >= 2:
print(r_id, "ranks", rank[r_id], "spread", spread)R2 ranks [2, 3, 1] spread 2
R3 ranks [3, 4, 5] spread 2
R4 ranks [5, 2, 4] spread 3
R6 ranks [4, 5, 3] spread 2পাস চেকলিস্ট: র্যাংকগুলো আগের ব্লকের সঙ্গে মেলে; R4-এর পার্থক্য সবচেয়ে বেশি। ভালো উত্তর যোগ করে যে এগুলো আলোচনার ঝুঁকি, উপেক্ষার নয়।
সারসংক্ষেপ
- ফিচারের পরিধি সংকীর্ণ রাখুন, পরিধির বাইরে কী তা লিখুন, এবং ব্যবহারকারী, প্রভাবিত অ-ব্যবহারকারী ও স্টেকহোল্ডারদের তালিকা করুন।
- ইনপুট, আউটপুট ও সিদ্ধান্ত-বিন্দু ম্যাপ করুন; যে সিদ্ধান্ত কোনো মানুষ নেয় না তা খুঁজুন।
- সহজাত ও অবশিষ্ট ঝুঁকি লিখুন, আর সম্ভাবনা, তীব্রতা, প্রত্যাবর্তনযোগ্যতা, এক্সপোজার, স্থায়িত্ব ও লেভেল আলাদা ফিল্ডে রাখুন।
- রিস্ক ম্যাট্রিক্স একটা বেছে নেওয়া পদ্ধতি: একাধিক নিয়মে র্যাংক করুন এবং যে ঝুঁকিগুলো নড়ে তা আলোচনা করুন।
- প্রতিটা স্কোরের পাশে অনুমান ও প্রমাণের মান লিখুন; চেকার ফাঁক জানায়, প্রমাণ বানায় না।
এরপর: ঝুঁকির স্কোরিং, নিয়ন্ত্রণ ও সিদ্ধান্ত: একটা টয় রিস্ক রেজিস্টার বানানো পাতায় এই রেজিস্টারে নিয়ন্ত্রণ, বিকল্প, ঝুঁকি গ্রহণের নিয়ম ও পুনর্মূল্যায়নের ট্রিগার যোগ হবে।