বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ

উত্স নোড: 1936824

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

অনেক উন্নত মেশিন লার্নিং মডেল যেমন র্যান্ডম ফরেস্ট বা গ্রেডিয়েন্ট বুস্টিং অ্যালগরিদম যেমন XGBoost, CatBoost, বা LightGBM (এবং এমনকি অটোএনকোডারএকটি গুরুত্বপূর্ণ সাধারণ উপাদানের উপর নির্ভর করুন: সিদ্ধান্ত গাছ!

ডিসিশন ট্রি না বুঝলে, উপরে উল্লিখিত অ্যাডভান্সড ব্যাগিং বা গ্রেডিয়েন্ট-বুস্টিং অ্যালগরিদমগুলির কোনওটিও বোঝা অসম্ভব, যা কোনও ডেটা বিজ্ঞানীর জন্য লজ্জাজনক! সুতরাং, আসুন পাইথনে একটি বাস্তবায়নের মাধ্যমে একটি সিদ্ধান্ত গাছের অভ্যন্তরীণ কাজগুলিকে রহস্যময় করি।

এই নিবন্ধে, আপনি শিখতে হবে

  • কেন এবং কিভাবে একটি সিদ্ধান্ত গাছ ডেটা বিভক্ত করে,
  • তথ্য লাভ, এবং
  • NumPy ব্যবহার করে পাইথনে সিদ্ধান্ত গাছগুলি কীভাবে বাস্তবায়ন করবেন।

আপনি কোড খুঁজে পেতে পারেন আমার Github.

ভবিষ্যদ্বাণী করার জন্য, সিদ্ধান্ত গাছের উপর নির্ভর করে বিদারক একটি পুনরাবৃত্ত ফ্যাশনে ছোট অংশে ডেটাসেট।

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

উপরের ছবিতে, আপনি একটি বিভাজনের একটি উদাহরণ দেখতে পারেন — আসল ডেটাসেট দুটি ভাগে বিভক্ত হয়ে যায়। পরবর্তী ধাপে, এই উভয় অংশ আবার বিভক্ত হয়, এবং তাই। এটি চলতে থাকে যতক্ষণ না কিছু ধরণের থামার মানদণ্ড পূরণ না হয়, উদাহরণস্বরূপ,

  • যদি বিভাজনের ফলে একটি অংশ খালি হয়
  • যদি একটি নির্দিষ্ট পুনরাবৃত্তি গভীরতা পৌঁছেছে
  • যদি (আগের বিভাজনের পরে) ডেটাসেটে শুধুমাত্র কয়েকটি উপাদান থাকে, যা আরও বিভাজন অপ্রয়োজনীয় করে তোলে।

কিভাবে আমরা এই বিভাজন খুঁজে পেতে পারি? এবং কেন আমরা এমনকি যত্ন? খুঁজে বের কর.

প্রেরণা

ধরা যাক আমরা একটি সমাধান করতে চাই বাইনারি শ্রেণীবিভাগ সমস্যা যে আমরা এখন নিজেদের তৈরি করি:

import numpy as np
np.random.seed(0) X = np.random.randn(100, 2) # features
y = ((X[:, 0] > 0) * (X[:, 1] < 0)) # labels (0 and 1)

দ্বি-মাত্রিক তথ্য এই মত দেখায়:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

আমরা দেখতে পাচ্ছি যে দুটি ভিন্ন শ্রেণী রয়েছে - প্রায় 75% ক্ষেত্রে বেগুনি এবং প্রায় 25% ক্ষেত্রে হলুদ। আপনি একটি সিদ্ধান্ত গাছ এই তথ্য ফিড যদি শ্রেণিবদ্ধ, এই গাছের প্রাথমিকভাবে নিম্নলিখিত চিন্তা আছে:

"দুটি ভিন্ন লেবেল আছে, যা আমার জন্য খুব অগোছালো। আমি ডেটা দুটি ভাগে বিভক্ত করে এই জগাখিচুড়িটি পরিষ্কার করতে চাই — এই অংশগুলি আগের সম্পূর্ণ ডেটার চেয়ে পরিষ্কার হওয়া উচিত।" - বৃক্ষ যে চেতনা অর্জন করেছে

এবং গাছ তাই করে।

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

গাছটি প্রায় বরাবর একটি বিভক্ত করার সিদ্ধান্ত নেয় x-অক্ষ এটির প্রভাব রয়েছে যে ডেটার উপরের অংশটি এখন পুরোপুরি পরিষ্কার, মানে আপনি শুধুমাত্র খুঁজে পান একটি একক ক্লাস (এই ক্ষেত্রে বেগুনি) সেখানে।

যাইহোক, নীচের অংশ এখনও আছে নোংরা, এক অর্থে আগের চেয়েও অগোছালো। সম্পূর্ণ ডেটাসেটে ক্লাসের অনুপাত প্রায় 75:25 ছিল, কিন্তু এই ছোট অংশে এটি প্রায় 50:50, যা এটি যতটা মিশ্রিত হয়

 বিঃদ্রঃ: এখানে, এটা কোন ব্যাপার না যে ছবিতে বেগুনি এবং হলুদ সুন্দরভাবে আলাদা করা হয়েছে। ঠিক বিভিন্ন লেবেলের কাঁচা পরিমাণ দুই ভাগে গণনা।

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

তবুও, এটি গাছের জন্য প্রথম পদক্ষেপ হিসাবে যথেষ্ট ভাল, এবং তাই এটি চলতে থাকে। যদিও এটি শীর্ষে আরেকটি বিভাজন তৈরি করবে না, পরিষ্কার অংশ আর, এটি পরিষ্কার করার জন্য নীচের অংশে আরেকটি বিভাজন তৈরি করতে পারে।

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

এবং voilà, তিনটি পৃথক অংশের প্রতিটি সম্পূর্ণরূপে পরিষ্কার, যেহেতু আমরা প্রতি অংশে শুধুমাত্র একটি একক রঙ (লেবেল) খুঁজে পাই।

এখন ভবিষ্যদ্বাণী করা সত্যিই সহজ: যদি একটি নতুন ডেটা পয়েন্ট আসে, আপনি কেবল কোনটি পরীক্ষা করুন৷ তিনটি অংশ এটা মিথ্যা এবং এটি সংশ্লিষ্ট রং দিতে. এটি এখন তাই ভাল কাজ করে কারণ প্রতিটি অংশ হয় পরিষ্কার. সহজ, তাই না?

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

ঠিক আছে, আমরা কথা বলছিলাম পরিষ্কার এবং নোংরা ডেটা কিন্তু এখন পর্যন্ত এই শব্দগুলি শুধুমাত্র কিছু অস্পষ্ট ধারণা উপস্থাপন করে। যেকোনো কিছু বাস্তবায়ন করতে হলে আমাদের সংজ্ঞায়িত করার উপায় খুঁজে বের করতে হবে পরিচ্ছন্নতা.

পরিচ্ছন্নতার জন্য ব্যবস্থা

আসুন আমরা ধরে নিই যে আমাদের কিছু লেবেল আছে, উদাহরণস্বরূপ

y_1 = [0, 0, 0, 0, 0, 0, 0, 0] y_2 = [1, 0, 0, 0, 0, 0, 1, 0]
y_3 = [1, 0, 1, 1, 0, 0, 1, 0]

স্বজ্ঞাতভাবে, y₁ লেবেলের পরিচ্ছন্ন সেট, এর পরে y₂ এবং তারপর y₃. এখন পর্যন্ত এত ভাল, কিন্তু কিভাবে আমরা এই আচরণ সংখ্যা রাখতে পারি? সম্ভবত সবচেয়ে সহজ জিনিস যা মনে আসে তা হল নিম্নলিখিত:

শুধু শূন্যের পরিমাণ এবং একের পরিমাণ গণনা করুন। তাদের পরম পার্থক্য গণনা. এটিকে আরও সুন্দর করতে, অ্যারের দৈর্ঘ্যের মাধ্যমে ভাগ করে এটিকে স্বাভাবিক করুন।

উদাহরণ স্বরূপ, y₂ মোট ৮টি এন্ট্রি আছে — ৬টি শূন্য এবং ২টি। অতএব, আমাদের কাস্টম-সংজ্ঞায়িত পরিচ্ছন্নতার স্কোর হবে |6 – 2| / 8 = 0.5। এটা যে পরিচ্ছন্নতার স্কোর গণনা করা সহজ y₁ এবং y₃ যথাক্রমে 1.0 এবং 0.0। এখানে, আমরা সাধারণ সূত্র দেখতে পারি:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

এখানে, n₀ এবং n₁ শূন্য এবং একের সংখ্যা যথাক্রমে, n = n₀ + n₁ অ্যারের দৈর্ঘ্য এবং p₁ = n₁ / n 1 লেবেলের ভাগ।

এই সূত্রের সাথে সমস্যা হল যে এটি দুটি শ্রেণীর ক্ষেত্রে বিশেষভাবে উপযোগী, কিন্তু খুব প্রায়ই আমরা বহু-শ্রেণীর শ্রেণীবিভাগে আগ্রহী। একটি সূত্র যা বেশ ভাল কাজ করে তা হল জিনি অপবিত্রতা পরিমাপ:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

বা সাধারণ ক্ষেত্রে:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

এটি এত ভাল কাজ করে যে স্কিট-লার্ন ডিফল্ট পরিমাপ হিসাবে এটি গৃহীত এটার জন্য DecisionTreeClassifier বর্গ.

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 বিঃদ্রঃ: জিনি পরিমাপ করে অগোছালোতা পরিচ্ছন্নতার পরিবর্তে। উদাহরণ: যদি একটি তালিকায় শুধুমাত্র একটি একক শ্রেণী থাকে (=খুব পরিষ্কার ডেটা!), তাহলে যোগফলের সমস্ত পদ শূন্য, তাই যোগফল শূন্য। সবচেয়ে খারাপ ঘটনা হল যদি সমস্ত ক্লাস সঠিক সংখ্যায় উপস্থিত হয়, সেক্ষেত্রে জিনি হয় 1-1/C কোথায় C ক্লাসের সংখ্যা।

এখন যেহেতু আমাদের পরিচ্ছন্নতা/অগোছালোতার জন্য একটি পরিমাপ আছে, আসুন দেখি কীভাবে এটি ভাল বিভাজন খুঁজে পেতে ব্যবহার করা যেতে পারে।

বিভক্ত খোঁজা

অনেকগুলি বিভাজন রয়েছে যা আমরা বেছে নিয়েছি, কিন্তু কোনটি ভাল? জিনি অপবিত্রতা পরিমাপের সাথে আবার আমাদের প্রাথমিক ডেটাসেট ব্যবহার করা যাক।

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

আমরা এখন পয়েন্ট গণনা করব না, তবে আসুন আমরা ধরে নিই যে 75% বেগুনি এবং 25% হলুদ। গিনির সংজ্ঞা ব্যবহার করে, সম্পূর্ণ ডেটাসেটের অপবিত্রতা

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

যদি আমরা ডেটাসেট বরাবর বিভক্ত করি x-অক্ষ, আগে যেমন করা হয়েছে:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

সার্জারির  উপরের অংশে 0.0 এর জিনি অশুদ্ধতা রয়েছে এবং নীচের অংশ

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

 

গড়ে, দুটি অংশে (0.0 + 0.5) / 2 = 0.25 এর জিনি অপরিষ্কার রয়েছে, যা আগের থেকে পুরো ডেটাসেটের 0.375 থেকে ভাল। এটাকে আমরা তথাকথিত পরিপ্রেক্ষিতেও প্রকাশ করতে পারি তথ্য লাভ:

এই বিভাজনের তথ্য লাভ হল 0.375 – 0.25 = 0.125।

যে হিসাবে সহজ. তথ্য লাভ যত বেশি হবে (অর্থাৎ জিনির অপবিত্রতা কম), তত ভালো।

বিঃদ্রঃ: আরেকটি সমান ভাল প্রাথমিক বিভাজন y-অক্ষ বরাবর হবে।

মনে রাখা একটি গুরুত্বপূর্ণ জিনিস এটি দরকারী হয় অংশগুলির আকার দ্বারা অংশগুলির জিনি অমেধ্য ওজন করুন. উদাহরণস্বরূপ, আসুন আমরা ধরে নিই

  • অংশ 1 50টি ডেটাপয়েন্ট নিয়ে গঠিত এবং একটি জিনি অশুদ্ধতা 0.0 এবং
  • পার্ট 2-এ 450টি ডেটাপয়েন্ট রয়েছে এবং 0.5 এর জিনি অশুদ্ধতা রয়েছে,

তাহলে গড় গিনি অপবিত্রতা (0.0 + 0.5) / 2 = 0.25 হওয়া উচিত নয় বরং 50 / (50 + 450) * 0.0 + 450 / (50 + 450) * 0.5 = 0.45.

ঠিক আছে, এবং কিভাবে আমরা সেরা বিভাজন খুঁজে পেতে পারি? সহজ কিন্তু চিন্তাশীল উত্তর হল:

শুধু সমস্ত বিভাজন চেষ্টা করে দেখুন এবং সর্বোচ্চ তথ্য লাভের সাথে একটি বেছে নিন। এটি মূলত একটি পাশবিক শক্তি পদ্ধতি।

আরও সুনির্দিষ্ট হতে, আদর্শ সিদ্ধান্ত গাছ ব্যবহার করে স্থানাঙ্ক অক্ষ বরাবর বিভক্ত, আমি xᵢ = গ কিছু বৈশিষ্ট্যের জন্য i এবং থ্রেশহোল্ড c. এই যে মানে

  • বিভক্ত ডেটার একটি অংশ সমস্ত ডেটা পয়েন্ট নিয়ে গঠিত সঙ্গে xᵢ গএবং
  • সমস্ত পয়েন্টের অন্য অংশ x সঙ্গে xᵢ ≥ গ.

এই সাধারণ বিভাজন নিয়মগুলি অনুশীলনে যথেষ্ট ভাল প্রমাণিত হয়েছে, তবে আপনি অবশ্যই অন্যান্য বিভাজন তৈরি করতে এই যুক্তিটিকে প্রসারিত করতে পারেন (যেমন তির্যক রেখাগুলি xᵢ + 2xⱼ = 3, উদাহরণ স্বরূপ).

দুর্দান্ত, এই সমস্ত উপাদান যা আমাদের এখন যেতে হবে!

আমরা এখন সিদ্ধান্ত বৃক্ষ বাস্তবায়ন করব। যেহেতু এটি নোড নিয়ে গঠিত, আসুন একটি সংজ্ঞায়িত করি Node ক্লাস প্রথম।

from dataclasses import dataclass @dataclass
class Node: feature: int = None # feature for the split value: float = None # split threshold OR final prediction left: np.array = None # store one part of the data right: np.array = None # store the other part of the data

একটি নোড বিভক্ত করার জন্য যে বৈশিষ্ট্যটি ব্যবহার করে তা জানে (feature) পাশাপাশি বিভাজন মান (value). value সিদ্ধান্ত গাছের চূড়ান্ত ভবিষ্যদ্বাণী করার জন্য একটি স্টোরেজ হিসাবেও ব্যবহৃত হয়। যেহেতু আমরা একটি বাইনারি ট্রি তৈরি করব, প্রতিটি নোডকে তার বাম এবং ডান বাচ্চাদের জানতে হবে, যেমনটি সংরক্ষণ করা হয়েছে left এবং right .

এখন, আসল সিদ্ধান্ত গাছ বাস্তবায়ন করা যাক। আমি এটিকে স্কিট-লার্ন সামঞ্জস্যপূর্ণ করে তুলছি, তাই আমি কিছু ক্লাস ব্যবহার করি sklearn.base . আপনি যদি এর সাথে পরিচিত না হন তবে আমার নিবন্ধটি দেখুন কীভাবে স্কিট-লার্ন সামঞ্জস্যপূর্ণ মডেল তৈরি করবেন.

বাস্তবায়ন করা যাক!

import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin class DecisionTreeClassifier(BaseEstimator, ClassifierMixin): def __init__(self): self.root = Node() @staticmethod def _gini(y): """Gini impurity.""" counts = np.bincount(y) p = counts / counts.sum() return (p * (1 - p)).sum() def _split(self, X, y): """Bruteforce search over all features and splitting points.""" best_information_gain = float("-inf") best_feature = None best_split = None for feature in range(X.shape[1]): split_candidates = np.unique(X[:, feature]) for split in split_candidates: left_mask = X[:, feature] split X_left, y_left = X[left_mask], y[left_mask] X_right, y_right = X[~left_mask], y[~left_mask] information_gain = self._gini(y) - ( len(X_left) / len(X) * self._gini(y_left) + len(X_right) / len(X) * self._gini(y_right) ) if information_gain > best_information_gain: best_information_gain = information_gain best_feature = feature best_split = split return best_feature, best_split def _build_tree(self, X, y): """The heavy lifting.""" feature, split = self._split(X, y) left_mask = X[:, feature] split X_left, y_left = X[left_mask], y[left_mask] X_right, y_right = X[~left_mask], y[~left_mask] if len(X_left) == 0 or len(X_right) == 0: return Node(value=np.argmax(np.bincount(y))) else: return Node( feature, split, self._build_tree(X_left, y_left), self._build_tree(X_right, y_right), ) def _find_path(self, x, node): """Given a data point x, walk from the root to the corresponding leaf node. Output its value.""" if node.feature == None: return node.value else: if x[node.feature] node.value: return self._find_path(x, node.left) else: return self._find_path(x, node.right) def fit(self, X, y): self.root = self._build_tree(X, y) return self def predict(self, X): return np.array([self._find_path(x, self.root) for x in X])

এবং এটাই! আপনি এখন স্কিট-লার্ন সম্পর্কে আপনার পছন্দের সমস্ত জিনিস করতে পারেন:

dt = DecisionTreeClassifier().fit(X, y)
print(dt.score(X, y)) # accuracy # Output
# 1.0

যেহেতু গাছটি অনিয়মিত, এটি অনেক বেশি ওভারফিটিং, তাই নিখুঁত ট্রেন স্কোর। অদেখা তথ্যে নির্ভুলতা আরও খারাপ হবে। আমরা গাছটি দেখতে কেমন তা এর মাধ্যমেও পরীক্ষা করতে পারি

print(dt.root) # Output (prettified manually):
# Node(
# feature=1,
# value=-0.14963454032767076,
# left=Node(
# feature=0,
# value=0.04575851730144607,
# left=Node(
# feature=None,
# value=0,
# left=None,
# right=None
# ),
# right=Node(
# feature=None,
# value=1,
# left=None,
# right=None
# )
# ),
# right=Node(
# feature=None,
# value=0,
# left=None,
# right=None
# )
# )

একটি ছবি হিসাবে, এটি হবে:

 

বাস্তবায়ন দ্বারা বোঝা: সিদ্ধান্ত বৃক্ষ
লেখকের ছবি

এই নিবন্ধে, আমরা দেখেছি কিভাবে সিদ্ধান্ত গাছ বিস্তারিতভাবে কাজ করে। আমরা কিছু অস্পষ্ট, তবুও স্বজ্ঞাত ধারণা দিয়ে শুরু করেছি এবং সেগুলিকে সূত্র এবং অ্যালগরিদমে পরিণত করেছি। শেষ পর্যন্ত, আমরা স্ক্র্যাচ থেকে একটি সিদ্ধান্ত গাছ বাস্তবায়ন করতে সক্ষম হয়েছি।

যদিও সতর্কতার একটি শব্দ: আমাদের সিদ্ধান্ত গাছ এখনও নিয়মিত করা যাবে না। সাধারণত, আমরা যেমন পরামিতি নির্দিষ্ট করতে চাই

  • সর্বোচ্চ গভীরতা
  • পাতার আকার
  • এবং ন্যূনতম তথ্য লাভ

অন্য অনেকের মধ্যে ভাগ্যক্রমে, এই জিনিসগুলি বাস্তবায়ন করা এতটা কঠিন নয়, যা এটিকে আপনার জন্য একটি নিখুঁত হোমওয়ার্ক করে তোলে। উদাহরণস্বরূপ, যদি আপনি উল্লেখ করেন leaf_size=10 একটি পরামিতি হিসাবে, তারপর 10 টির বেশি নমুনা ধারণকারী নোড আর বিভক্ত করা উচিত নয়। এছাড়াও, এই বাস্তবায়ন হয় দক্ষ নয়. সাধারণত, আপনি নোডগুলিতে ডেটাসেটের অংশগুলি সংরক্ষণ করতে চান না, তবে পরিবর্তে শুধুমাত্র সূচকগুলি। সুতরাং আপনার (সম্ভাব্যভাবে বড়) ডেটাসেট শুধুমাত্র একবার মেমরিতে থাকে।

ভাল জিনিস আপনি এই সিদ্ধান্ত গাছ টেমপ্লেট সঙ্গে এখন পাগল হতে পারেন. আপনি করতে পারেন:

  • তির্যক বিভাজন প্রয়োগ করুন, যেমন xᵢ + 2xⱼ = 3 পরিবর্তে ন্যায়বিচার xᵢ = 3,
  • পাতার ভিতরে ঘটে যাওয়া যুক্তি পরিবর্তন করুন, অর্থাৎ আপনি সংখ্যাগরিষ্ঠ ভোট দেওয়ার পরিবর্তে প্রতিটি পাতার মধ্যে একটি লজিস্টিক রিগ্রেশন চালাতে পারেন, যা আপনাকে একটি রৈখিক গাছ
  • বিভক্ত করার পদ্ধতি পরিবর্তন করুন, যেমন পাশবিক শক্তি করার পরিবর্তে, কিছু এলোমেলো সংমিশ্রণ চেষ্টা করুন এবং সেরাটি বেছে নিন, যা আপনাকে একটি অতিরিক্ত-বৃক্ষ শ্রেণীবদ্ধকারী
  • এবং আরও অনেক কিছু.

 
 
ডঃ রবার্ট কুবলার পাবলিসিস মিডিয়ার একজন ডেটা সায়েন্টিস্ট এবং টুওয়ার্ডস ডেটা সায়েন্সের লেখক৷

 
মূল। অনুমতি নিয়ে পোস্ট করা।
 

সময় স্ট্যাম্প:

থেকে আরো কেডনুগেটস

KDnuggets ™ নিউজ 21: n30, আগস্ট 11: সর্বাধিক প্রচলিত ডেটা সায়েন্স ইন্টারভিউ প্রশ্ন ও উত্তর; কিভাবে ভিজ্যুয়ালাইজেশন এক্সপ্লোরেটরি ডেটা বিশ্লেষণে রূপান্তর করছে

উত্স নোড: 1015283
সময় স্ট্যাম্প: আগস্ট 11, 2021

অ্যামাজন ওয়েব সার্ভিসেস ওয়েবিনার: স্বাস্থ্যসেবা তথ্যের সাথে ক্লিনিকাল ট্রায়াল এবং বায়োমেডিক্যাল ডেভেলপমেন্ট প্রসেসকে ত্বরান্বিত করা

উত্স নোড: 1864939
সময় স্ট্যাম্প: আগস্ট 18, 2021