AI ওয়াটারমার্কিংয়ের অস্ত্র প্রতিযোগিতা
এটি ইংরেজি মূল লেখাটির মেশিন অনুবাদ। কিছু সূক্ষ্মতা হারিয়ে যেতে পারে, তাই ইংরেজি সংস্করণই মূল রেফারেন্স। মূল ইংরেজি লেখাটি পড়ুন
সংক্ষেপে
আমি Anthropic-এর ঘোষণা পড়ে জিরো-উইডথ ক্যারেক্টার খুঁজতে একটি টেক্সট এডিটর খুললাম। একটিও নেই। ওয়াটারমার্ক অক্ষরের মাঝে লুকিয়ে নেই, তা আছে মডেল কোন শব্দগুলো বেছেছে তার মধ্যে, আর এটা মেনে নিতে আমার যতটা সময় লাগার কথা তার চেয়ে বেশি লেগেছে। এর মানে “ওয়াটারমার্ক রিমুভার”-এর সরানোর মতো কিছুই নেই।
যে তিনটি জিনিসকে লোকে ওয়াটারমার্ক বলে
তিনটি ভিন্ন প্রক্রিয়াকে ওয়াটারমার্ক বলা হয়। তাদের মধ্যে প্রায় কিছুই মিল নেই।
| প্রক্রিয়া | সংকেত কোথায় থাকে | কীভাবে সরানো যায় | তৃতীয় পক্ষ শনাক্ত করতে পারে কি |
|---|---|---|---|
| লুকানো Unicode | জিরো-উইডথ ক্যারেক্টার, Tag ব্লক (U+E0000-E007F) | যেকোনো খুঁজে-বদলাও | হ্যাঁ, অতি সহজে |
| C2PA মেটাডেটা | ফাইল কন্টেনারে স্বাক্ষরিত ম্যানিফেস্ট | স্ক্রিনশট, আবার সেভ, ফরম্যাট রূপান্তর | হ্যাঁ, উপস্থিতি যাচাই করা যায় |
| পরিসংখ্যানগত ওয়াটারমার্ক | মডেলের টোকেন বাছাই | ভারী প্যারাফ্রেজ বা অনুবাদ | না, ভেন্ডরের কী লাগে |
Anthropic এর মধ্যে দুটি প্রয়োগ করে। ফাইলে .svg, .png ও .jpg-তে C2PA স্বাক্ষরিত উৎস-মেটাডেটা থাকে, একই Content Credentials মানদণ্ড যা ইমেজিং শিল্প ব্যবহার করে। টেক্সট পায় তৃতীয় সারিটি। Anthropic-এর ডকুমেন্টেশন একে বলে “সরাসরি টেক্সটের ভেতরে একটি অদৃশ্য ওয়াটারমার্ক”, যা “Claude-এর উত্তরের অর্থ, মান বা পাঠযোগ্যতা বদলায় না”।
রোলআউট শিরোনামে যা মনে হয়েছিল তার চেয়ে সংকীর্ণ। Anthropic-এর ভাষায়, “2 আগস্ট 2026 বা তার পরে চালু হওয়া Claude মডেলগুলো লঞ্চের সময় যন্ত্র-পাঠযোগ্য চিহ্ন সমর্থন করবে”, আর বিদ্যমান মডেলের সমর্থন চলমান বলে বর্ণিত। বিভাজনটি মডেল ধরে, দেশ ধরে নয়। একই পাতায় বলা আছে, যেখানেই Claude পাওয়া যায় সেখানে সমর্থিত মডেলের আউটপুটে চিহ্ন প্রযোজ্য, বিশ্বজুড়ে। চালিকা শক্তি হলো EU AI Act-এর অনুচ্ছেদ 50(2)-এর AI-উৎপন্ন কনটেন্টের স্বচ্ছতা বিষয়ক আচরণবিধি, যাতে Anthropic মডেল প্রদানকারী ও জেনারেটিভ AI সিস্টেম প্রদানকারী উভয় হিসেবে স্বাক্ষর করেছে। তাই এটা আসে মডেল ধরে ধরে, আর কোনো মডেলে এলে সবাই পায়।
Google কিছুদিন ধরে Gemini জুড়ে SynthID-Text চালাচ্ছে, আর পদ্ধতিটি Nature-এ প্রকাশিত। এটি শুধু স্যাম্পলিং ধাপ বদলায়। প্রতিটি টোকেনে এটি আগের k টোকেন দিয়ে একটি ছদ্ম-যাদৃচ্ছিক ফাংশন সিড করে, শব্দভান্ডারকে m স্তরের একটি টুর্নামেন্ট ব্র্যাকেটে ভাগ করে, আর যে টোকেনগুলো ম্যাচ জিততে থাকে তাদের স্যাম্পলিং সম্ভাবনা বাড়ায়। এটি যথেষ্ট স্তর ও যথেষ্ট টোকেনে ছড়িয়ে দিলে একটি পরিমাপযোগ্য পরিসংখ্যানগত ঝোঁক পাওয়া যায়, অথচ পাঠকের কাছে কোনো একক শব্দ ভুল লাগে না।
শনাক্তকরণে মূল মডেল লাগে না। লাগে কী-যুক্ত ছদ্ম-যাদৃচ্ছিক ফাংশন আর প্রার্থী টেক্সটের ওপর একটি স্কোরিং পাস। ওয়াটারমার্কযুক্ত টেক্সটে ওয়াটারমার্কহীন টেক্সটের চেয়ে নিয়মিতভাবে বেশি g-মান থাকে, আর সেই ব্যবধানই একটি হাইপোথিসিস টেস্ট।
এখানেই আমি আটকে গেলাম।
Signal Verifiable in a browser? Why
------ ------------------------ ---
Zero-width chars Yes Codepoint scan
Unicode Tag block Yes Codepoint scan, decodes to ASCII
C2PA manifest Presence, not signature JUMBF box / PNG caBX chunk markers
Claude watermark NO Keyed detector, method unpublished
SynthID-Text NO Keyed detector, Google-side API
Anthropic-এর সহায়তা পাতা এ বিষয়ে সোজাসুজি। তারা বলে তারা “ব্যবহারকারী ও অন্য তৃতীয় পক্ষকে Claude-এর এমবেড করা ওয়াটারমার্ক ও উৎস-মেটাডেটা শনাক্ত করতে সক্ষম করার জন্য কাজ করছে” এবং “আসন্ন প্রযুক্তিগত ডকুমেন্টেশনে শনাক্তকরণ প্রক্রিয়ার বিবরণ জানাবে”। আসন্ন মানে এখনও নেই। যতদিন তা না আসে, Anthropic-এর বাইরে কেউ টেক্সট ওয়াটারমার্ক যাচাই করতে পারে না। যে টুলই যাচাইয়ের দাবি করে, সে অন্য নামে একটি সাধারণ AI-টেক্সট ক্লাসিফায়ার চালাচ্ছে, আর সেগুলোর ত্রুটির প্রোফাইল অনেক খারাপ, বিশেষত দ্বিতীয় ভাষায় যাঁরা লেখেন তাঁদের ক্ষেত্রে।
ইন্সপেক্টর বানানো
তাই আমি এমন সংস্করণ বানালাম যা কেবল সেটুকুই দাবি করে যা যাচাই করতে পারে।
C2PA অংশে সবচেয়ে বেশি কোড লেগেছে। একটি Content Credentials ম্যানিফেস্ট থাকে JUMBF বক্সে, আর সেই বক্স কোথায় পড়বে তা কন্টেনারের ওপর নির্ভর করে। PNG তাকে caBX চাঙ্কে রাখে, JPEG তাকে APP11 সেগমেন্টে বহন করে, SVG তাকে XML মেটাডেটায় রাখে। খুঁজতে মানে কাঁচা বাইটে ওই মার্কারগুলো স্ক্যান করা:
const scanC2PA = (buf: ArrayBuffer) => {
const bytes = new Uint8Array(buf);
const find = (needle: string): boolean => {
const n = needle.length;
const limit = bytes.length - n;
for (let i = 0; i <= limit; i++) {
let ok = true;
for (let j = 0; j < n; j++) {
if (bytes[i + j] !== needle.charCodeAt(j)) { ok = false; break; }
}
if (ok) return true;
}
return false;
};
const isPng = bytes[0] === 0x89 &&
String.fromCharCode(bytes[1], bytes[2], bytes[3]) === 'PNG';
const hasJumbf = find('jumb') || find('jumd');
const hasC2pa = find('c2pa');
const hasPngBox = isPng && find('caBX');
return { found: hasC2pa || hasPngBox, hasJumbf, isPng };
};
এটি ম্যানিফেস্ট খুঁজে পায়। যাচাই করে না। যাচাইয়ের মানে সার্টিফিকেট চেইন ও ক্রিপ্টোগ্রাফিক স্বাক্ষর পরীক্ষা, আর আমি ব্রাউজার ট্যাবে হাতে লিখে তা নির্ভরযোগ্য বলে হাজির করতে যাচ্ছি না। টুলটি তা বলে এবং অফিসিয়াল ভেরিফায়ারের দিকে ইঙ্গিত করে।
স্ক্যানের একটি ব্যর্থতার ধরন আছে যা আমি চেপে না রেখে নাম নিয়ে বলতে চাই। কাঁচা বাইটে চার অক্ষরের মার্কার খুঁজলে ভুল পজিটিভ আসতে পারে, কারণ সংকুচিত ইমেজ ডেটার ভেতরে c2pa বাইট এসে যাওয়া ঠেকানোর কিছু নেই। তবুও আমি এটি রেখেছি। তিনটি ফরম্যাটে কন্টেনারের কাঠামো ঠিকভাবে পার্স করা এমন একটি টুলের জন্য অনেক পরিশ্রম, যার পুরো দাবি কেবল “এখানে কিছু আছে, অন্য কোথাও গিয়ে যাচাই করুন”। এটি একটি স্মোক অ্যালার্ম, আর লেবেল তা-ই বলে। কখনও এটি যথেষ্ট না হলে সমাধান আসল বক্স পার্সিং।
টেক্সট অংশ আমার লুকানো-টেক্সট ইন্সপেক্টরের কোডপয়েন্ট স্ক্যানার আবার ব্যবহার করে: জিরো-উইডথ ক্যারেক্টার, Unicode Tag ব্লক যা পুরো ASCII বার্তা অদৃশ্যভাবে পাচার করতে পারে, bidi কন্ট্রোল, ভ্যারিয়েশন সিলেক্টর। সবই শনাক্তযোগ্য এবং চিহ্নিত করার যোগ্য।
সবচেয়ে কঠিন অংশ কোড ছিল না। ছিল একটিমাত্র রায়ের ভাষা। টুলে “পরিসংখ্যানগত ওয়াটারমার্ক (Claude, SynthID-Text)” নামে একটি সংকেত আছে যার স্থায়ী অবস্থা not checkable, সঙ্গে ব্যাখ্যা। প্রকাশের আগে আমি এ নিয়ে বেশ কিছুক্ষণ ভেবেছি, কারণ “আমি জানি না” বলা টুলকে ভাঙা মনে হয়।
এটা গুরুত্বপূর্ণ কারণ এই ফলাফল কীভাবে ব্যবহৃত হয়। কেউ একটি অনুচ্ছেদ চেকারে চালায় আর আউটপুট নিয়ে যায় শৃঙ্খলা বৈঠকে। তাই টুল শুরুই করে চারটি সতর্কতা দিয়ে। প্রথম দুটি Anthropic-এর নিজস্ব অবস্থান, বাকি দুটি আমার, আমি যা বানিয়েছি তার সীমা নিয়ে:
- চিহ্ন না থাকা মানব-রচনার প্রমাণ নয়। Anthropic ভারী সম্পাদনা, প্যারাফ্রেজ, অনুবাদ ও অন্য লেখার সঙ্গে মেশানোকে এমন অবস্থা বলে যেখানে চিহ্ন শনাক্ত করা যায় না।
- চিহ্ন থাকাও লেখকত্বের প্রমাণ নয়। এটি ইঙ্গিত দেয় বিষয়বস্তু ওই মডেলের মধ্য দিয়ে গিয়ে থাকতে পারে, কে লিখেছে বা মানুষ কতটা বদলেছে তা নয়।
- C2PA উপস্থিতি মানে C2PA যাচাই নয়। এই টুল ম্যানিফেস্ট খুঁজে পায়, স্বাক্ষর বা ইস্যুকারীকে যাচাই করে না।
- মেটাডেটা ভঙ্গুর। স্ক্রিনশট, আবার সেভ আর বেশির ভাগ প্ল্যাটফর্ম আপলোড তা মুছে দেয়, তাই ডাউনলোড করা ফাইলে পরিষ্কার ফলাফল মূল সম্পর্কে খুব কমই বলে।
নথির দুজন লেখক থাকলে কী হয়
ওপরের সবকিছু ধরে নেয় প্রতি নথিতে একজন লেখক। প্রায় কোনোটিরই একজন নয়। লোকে এক চ্যাটবটে খসড়া করে, আরেকটিতে একটি অংশ নতুন করে লেখে, সেই সপ্তাহে যেটা সবচেয়ে সস্তা তার জন্য OpenRouter দিয়ে রুট করে, আর মাঝে হাতে সম্পাদনা করে। এটা কোনো বিরল ফাঁকি দেওয়ার কৌশল নয়, এখন মানুষ এভাবেই কাজ করে।
Anthropic আগেই এটি চিহ্নিত করে। চিহ্ন শনাক্ত করা যায় না এমন অবস্থার তালিকায় আছে সেই টেক্সট যা “ভারীভাবে সম্পাদিত, প্যারাফ্রেজ করা, অনূদিত, বা অন্য লেখার সঙ্গে মেশানো”। তারা এটিকে সতর্কতা হিসেবে বলে সেখানেই ছেড়ে দেয়। প্রক্রিয়াটা এই, কারণ প্রভাবের আকার আমাকে অবাক করেছিল।
গ্রিন-লিস্ট শনাক্তকরণ টোকেন গণনার ওপর একটি z-টেস্ট। এটি প্রকাশিত গ্রিন-লিস্ট গঠন, দৃষ্টান্তমূলক প্যারামিটার সহ। এটি Anthropic-এর পদ্ধতি নয়, যা অপ্রকাশিত। ফলাফলের ধরনটা অন্যত্রও খাটে, নির্দিষ্ট সংখ্যাগুলো নয়।
z = (greens - γT) / sqrt(T · γ(1-γ))
ধরুন একটি নথিতে T টোকেন, যার মধ্যে কেবল W যন্ত্র-লিখিত, প্রতিটি গ্রিন হারকে ε দিয়ে বাড়ায়। মানব টোকেনগুলো ভিত্তি হার γ-তে গ্রিন দেয়, তাই প্রত্যাশিত গ্রিন গণনা Tγ + Wε এবং পুরো নথির পরিসংখ্যান হয়:
z_whole = W·ε / sqrt(T · γ(1-γ))
এবার কেবল যন্ত্র-লিখিত অংশকে স্কোর করুন:
z_span = ε·sqrt(W) / sqrt(γ(1-γ))
একটিকে অন্যটি দিয়ে ভাগ করলে সবকিছু কেটে একটিমাত্র সংখ্যা হয়:
z_whole / z_span = sqrt(W/T) = sqrt(f)
পুরো নথি স্কোর করলে আপনার শনাক্তকরণ ক্ষমতার কেবল যন্ত্র-লিখিত অংশের বর্গমূল থাকে। f = 0.1-এ তা 32%। দৃষ্টান্ত হিসেবে γ = 0.5 ও গ্রিন হারের বৃদ্ধি ε = 0.25 ধরলে, 300 টোকেনের যন্ত্র-লিখিত অংশ একাই z = 8.66 পায় এবং বিপুল আস্থায় চিহ্নিত হবে। একই অংশ 3,000 টোকেনের নথিতে বসালে পুরো নথির স্কোর নেমে যায় z = 2.74-এ, যেকোনো যুক্তিসংগত সীমার নিচে। ওয়াটারমার্ক বদলায়নি। ডিটেক্টর ভুল একক মেপেছে।
অর্থাৎ মডেল মেশানো ওয়াটারমার্ককে আক্রমণ করে না। এটি ডিটেক্টরের উইন্ডো বাছাইকে আক্রমণ করে।
প্রতিকার হলো টেক্সটের ওপর দিয়ে একটি উইন্ডো সরানো এবং সর্বোচ্চ স্কোর নেওয়া, মোটামুটি SynthID-Text পেপারের WinMax ধারণা। এটি কাজ করে, আর এর একটা দামও আছে। শত শত ওভারল্যাপিং উইন্ডো স্কোর করা মানে একটি নথিতে শত শত হাইপোথিসিস টেস্ট চালানো, তাই বহু-তুলনা সংশোধন লাগে, যা সীমা ওপরে ঠেলে, যা ছোট অংশগুলোকে আবার তার নিচে ঠেলে দেয়। আপনি পাতলা হয়ে যাওয়ার সমস্যা বদলে নেন ভুল পজিটিভের সমস্যায়, আর ওই ডায়ালের কোনো সেটিংই দুটোকে এড়াতে পারে না।
উইন্ডোইংও কেবল একজন ভেন্ডরের চিহ্নই স্থানীয়করণ করে, কারণ প্রতিটি প্রদানকারী নিজের ছদ্ম-যাদৃচ্ছিক ফাংশন নিজের কী দিয়ে চালায়:
for each vendor key K: # Anthropic, Google, OpenAI, ...
for each window w in text: # all of these need FPR correction together
score(w, K)
তিন ভেন্ডর মানে তিন ডিটেক্টর, তিনটি API রাউন্ড-ট্রিপ, তিনটি আস্থার সম্পর্ক। কেউ ফেডারেটেড সংস্করণ দেয় না, আর Anthropic-এরটি এখনও প্রকাশ্য নয়।
তারপর আছে সেই ফাঁক যা ওপরের বেশির ভাগকে তাত্ত্বিক করে তোলে। ওয়াটারমার্কিংয়ের জন্য যিনি ইনফারেন্স চালান তাঁকে স্যাম্পলিং ধাপে ঝোঁক দিতে হয়। এটি সার্ভিং স্ট্যাকের বৈশিষ্ট্য, টেক্সটের নয়। তাই:
| পথ | ওয়াটারমার্ক আছে? | কেন |
|---|---|---|
| OpenRouter → Claude / Gemini / GPT | হ্যাঁ | টোকেন ফেরত দেওয়ার আগে প্রদানকারীর স্ট্যাকের ভেতরে প্রয়োগ হয়। রাউটার শুধু তৈরি টেক্সট এগিয়ে দেয়। |
| OpenRouter → Llama / Qwen / Mistral | না | ওপেন-ওয়েট ইনফারেন্স প্রদানকারীদের ঝোঁকযুক্ত স্যাম্পলিং বাস্তবায়নের কোনো দায় বা প্রণোদনা নেই। |
| আপনার নিজের ল্যাপটপে llama.cpp | না | একই কারণ, আরও বেশি। |
OpenRouter রুটিং করে ওয়াটারমার্ককে হারায় না। সে এমন একটি লেন দেয় যেখানে চিহ্ন কখনও বসানোই হয়নি, আর সেই লেন একটি কনফিগ স্ট্রিং দূরে। তাই এই ব্যবস্থা মালিকানাধীন ফ্রন্টিয়ার API-গুলোকে ঘিরে, আর কিছু নয়। ওপেন ওয়েট বেশির ভাগ টেক্সট কাজের জন্য ইতিমধ্যে যথেষ্ট ভালো এবং এর বাইরে। যে এটি এড়াতে চায় সে একটি মডেলের নাম বদলে নেয়।
আমি পাতলা হয়ে যাওয়ার এই প্রভাব ইন্সপেক্টরে তৃতীয় ট্যাব হিসেবে যোগ করেছি। এটি কোনো টেক্সট বিশ্লেষণ করে না, কিছু শনাক্তও করে না। এটি z-টেস্টের অঙ্ক চালায় এবং যন্ত্র-লিখিত অংশ বদলালে দুটি স্কোর কীভাবে আলাদা হয়ে যায় তা এঁকে দেখায়।
আমি যা ভুল করেছিলাম
আমি ধরে নিয়েছিলাম ওয়াটারমার্কের স্থায়িত্ব মানে সম্পাদনা টিকে থাকা। আসলে তা প্যারাফ্রেজ টিকে থাকা, যা অনেক কঠিন, কারণ প্যারাফ্রেজ টোকেন বাছাই আবার স্যাম্পল করে আর সংকেত সেখানেই থাকে। আমার প্রথম পরিকল্পনা ছিল আস্থা শতাংশসহ একটি চেকার, কারণ লোকে তা-ই আশা করে এবং তা-ই র্যাঙ্ক করে। আমি সেটা বাতিল করলাম। আমার বানানো যেকোনো সংখ্যা হতো অন্য লেবেলে একটি সাধারণ AI-টেক্সট ক্লাসিফায়ার, আর সেগুলোর নথিভুক্ত ভুল পজিটিভের সমস্যা আছে, বিশেষত অ-মাতৃভাষী ইংরেজি লেখকদের বিরুদ্ধে।
আরও কঠিন সিদ্ধান্ত ছিল ওয়াটারমার্ক রিমুভার, পাশের সেই পণ্য যা সত্যিই ট্র্যাফিক টানত। আমি তা বাদ দিয়েছি। সেটি হতো প্রকাশ-প্রতিশ্রুতি এড়ানোর টুল, আর যেভাবেই হোক কাজ করে না। সরানোর মতো কোনো অক্ষর নেই, তাই সরানো মানে এমন আক্রমণাত্মক প্যারাফ্রেজ যা লেখার মান নষ্ট করে, আর ব্রাউজার টুল মডেল না পাঠিয়ে প্যারাফ্রেজ করতে পারে না। পরে যা আমার মনে থেকে গেছে তা হলো, এর সঙ্গে আমি যে যতিচিহ্ন-ঠিককারী টুল প্রকাশ করেছি সেটি জিরো-উইডথ ক্যারেক্টার সরিয়ে দেয়, যা আমি নিজের খসড়ার জন্য চেয়েছিলাম। একই রেজেক্স, ভিন্ন পণ্য। রেখাটা পাতা টুলের উদ্দেশ্য সম্পর্কে কী বলে তার মধ্যে, কোডে নয়।
এটি কোথায় যায়
যে উৎস-সংকেত কেবল ভেন্ডর যাচাই করতে পারে তা এখনও সার্বজনীন উৎস-প্রমাণ নয়। কিন্তু sqrt(f) ফলাফল “অস্ত্র প্রতিযোগিতা চলছে” বলার চেয়ে আরও নির্দিষ্ট কিছুর দিকে ইঙ্গিত করে। শুধু বিষয়বস্তুভিত্তিক ফরেনসিক সেই প্রশ্নের উত্তর দিতে পারে না যা লোকে আসলে করে। টেক্সট তৈরি হয়েছে কি না নয়, বরং কোন অংশগুলো, কার দ্বারা, আর মানুষ কতটা বদলেছে। আমাদের হাতে থাকা প্রতিটি প্রক্রিয়া ঠিক সেখানেই নষ্ট হয় যেখানে বাস্তব নথি থাকে, একাধিক লেখক ও দীর্ঘ সম্পাদনা-ইতিহাসের ফাইলে। তাই কাজের পথ সম্ভবত টেক্সটকে জেরা করা বন্ধ করে পরিবেশ রেকর্ড করা শুরু করা। এডিটরের রিভিশন ইতিহাস, কমিট টাইমলাইন আর স্বাক্ষরিত সম্পাদনা-শৃঙ্খল ইতিমধ্যেই সমাপ্ত কাজের ওপর যেকোনো পরিসংখ্যানগত পরীক্ষার চেয়ে অনেক বেশি নির্ভরযোগ্যভাবে লেখকত্ব ধরে রাখে, আর C2PA-এর অ্যাসার্শন মডেল ঠিক এই ধরনের স্তরযুক্ত, বহু-অভিনেতার উৎস-প্রমাণকে লক্ষ্য করে। ওয়াটারমার্কের আসল কাজ শিরোনামের ইঙ্গিতের চেয়ে সংকীর্ণ। এটি সেই যন্ত্র-টেক্সটের জন্য ডিফল্ট-অন প্রকাশ যা কেউ লুকোনোর চেষ্টা করছে না, আর প্রতিপক্ষ-সৃষ্ট ক্ষেত্রগুলো সামলানো হয় যেখানে তা সবসময় হওয়া উচিত ছিল, সেই টুলগুলোতে যা নথি কীভাবে তৈরি হয়েছে তা রেকর্ড করে।
লেখক
Rohit BuraniAI ইঞ্জিনিয়ার, যিনি ল্যাব থেকে লোকাল-ফার্স্ট সিস্টেম, সেলফ-হোস্টেড অবকাঠামো এবং স্বয়ংক্রিয় টুল তৈরি করেন।