পাঠ্য শ্রেণীবদ্ধ করতে একটি ফ্লাটার অ্যাপ তৈরি করুন

1. আপনি শুরু করার আগে

এই কোডল্যাবে, আপনি REST এবং gRPC-এর মাধ্যমে TensorFlow সার্ভিং সহ একটি Flutter অ্যাপ থেকে কীভাবে পাঠ্য-শ্রেণীবিভাগের অনুমান চালাতে হয় তা শিখবেন।

পূর্বশর্ত

আপনি কি শিখবেন

  • কীভাবে একটি সাধারণ ফ্লাটার অ্যাপ তৈরি করবেন এবং টেনসরফ্লো সার্ভিং (REST এবং gRPC) এর মাধ্যমে পাঠ্যকে শ্রেণিবদ্ধ করবেন।
  • কিভাবে UI এ ফলাফল প্রদর্শন করবেন।

আপনি কি প্রয়োজন হবে

2. আপনার ফ্লটার ডেভেলপমেন্ট এনভায়রনমেন্ট সেট আপ করুন

ফ্লাটার ডেভেলপমেন্টের জন্য, এই ল্যাবটি সম্পূর্ণ করার জন্য আপনার দুটি টুকরো সফ্টওয়্যার দরকার— ফ্লাটার SDK এবং একজন সম্পাদক

আপনি এই ডিভাইসগুলির যেকোনো একটি ব্যবহার করে কোডল্যাব চালাতে পারেন:

  • আইওএস সিমুলেটর (এক্সকোড সরঞ্জাম ইনস্টল করা প্রয়োজন)।
  • অ্যান্ড্রয়েড এমুলেটর (অ্যান্ড্রয়েড স্টুডিওতে সেটআপ প্রয়োজন)।
  • একটি ব্রাউজার (ডিবাগিংয়ের জন্য Chrome প্রয়োজন)।
  • একটি Windows , Linux , বা macOS ডেস্কটপ অ্যাপ্লিকেশন হিসাবে। আপনি যে প্ল্যাটফর্মে স্থাপন করার পরিকল্পনা করছেন সেখানে আপনাকে অবশ্যই বিকাশ করতে হবে। সুতরাং, আপনি যদি একটি উইন্ডোজ ডেস্কটপ অ্যাপ বিকাশ করতে চান, তাহলে যথাযথ বিল্ড চেইন অ্যাক্সেস করতে আপনাকে অবশ্যই উইন্ডোজে বিকাশ করতে হবে। অপারেটিং সিস্টেম-নির্দিষ্ট প্রয়োজনীয়তা রয়েছে যা docs.flutter.dev/desktop- এ বিস্তারিতভাবে কভার করা হয়েছে।

3. সেট আপ করুন

এই কোডল্যাবের জন্য কোড ডাউনলোড করতে:

  1. এই কোডল্যাবের জন্য GitHub সংগ্রহস্থলে নেভিগেট করুন।
  2. এই কোডল্যাবের জন্য সমস্ত কোড ডাউনলোড করতে কোড > জিপ ডাউনলোড করুন ক্লিক করুন।

2cd45599f51fb8a2.png

  1. আপনার প্রয়োজনীয় সমস্ত সংস্থান সহ একটি codelabs-main রুট ফোল্ডার আনপ্যাক করতে ডাউনলোড করা জিপ ফাইলটি আনজিপ করুন।

এই কোডল্যাবের জন্য, আপনার শুধুমাত্র সংগ্রহস্থলের tfserving-flutter/codelab2 সাবডিরেক্টরিতে ফাইল দরকার, যেখানে দুটি ফোল্ডার রয়েছে:

  • starter ফোল্ডারে স্টার্টার কোড থাকে যা আপনি এই কোডল্যাবের জন্য তৈরি করেন।
  • finished ফোল্ডারে সমাপ্ত নমুনা অ্যাপ্লিকেশনের জন্য সম্পূর্ণ কোড রয়েছে।

4. প্রকল্পের জন্য নির্ভরতা ডাউনলোড করুন

  1. ভিএস কোডে, ফাইল > ফোল্ডার খুলুন ক্লিক করুন এবং তারপরে আপনি আগে ডাউনলোড করা সোর্স কোড থেকে starter ফোল্ডারটি নির্বাচন করুন।
  2. আপনি যদি একটি ডায়ালগ দেখতে পান যা আপনাকে স্টার্টার অ্যাপের জন্য প্রয়োজনীয় প্যাকেজগুলি ডাউনলোড করতে অনুরোধ করে, তাহলে প্যাকেজ পান ক্লিক করুন।
  3. আপনি যদি এই ডায়ালগটি দেখতে না পান, আপনার টার্মিনাল খুলুন এবং তারপর starter ফোল্ডারে flutter pub get কমান্ড চালান।

7ada07c300f166a6.png

5. স্টার্টার অ্যাপ চালান

  1. VS কোডে, নিশ্চিত করুন যে Android এমুলেটর বা iOS সিমুলেটর সঠিকভাবে সেট আপ করা হয়েছে এবং স্ট্যাটাস বারে প্রদর্শিত হচ্ছে।

উদাহরণস্বরূপ, আপনি যখন Android এমুলেটরের সাথে Pixel 5 ব্যবহার করেন তখন আপনি যা দেখতে পান তা এখানে:

9767649231898791.png

আপনি যখন iOS সিমুলেটরের সাথে iPhone 13 ব্যবহার করেন তখন আপনি যা দেখেন তা এখানে:

95529e3a682268b2.png

  1. ক্লিক a19a0c68bc4046e6.png ডিবাগিং শুরু করুন

অ্যাপটি চালান এবং অন্বেষণ করুন

অ্যাপটি আপনার অ্যান্ড্রয়েড এমুলেটর বা iOS সিমুলেটরে চালু করা উচিত। UI বেশ সোজা। একটি পাঠ্য ক্ষেত্র রয়েছে যা ব্যবহারকারীকে পাঠ্য টাইপ করতে দেয়। ব্যবহারকারী REST বা gRPC-এর মাধ্যমে ব্যাকএন্ডে ডেটা পাঠাবেন কিনা তা বেছে নিতে পারেন। ব্যাকএন্ড একটি TensorFlow মডেল ব্যবহার করে প্রি-প্রসেসড ইনপুটে পাঠ্য শ্রেণিবিন্যাস সম্পাদন করে এবং ক্লায়েন্ট অ্যাপে শ্রেণিবিন্যাসের ফলাফল ফেরত দেয়, যা পালাক্রমে UI আপডেট করে।

b298f605d64dc132.pngd3ef3ccd3c338108.png

আপনি যদি ক্লাসিফায় ক্লিক করেন তবে কিছুই হবে না কারণ এটি এখনও ব্যাকএন্ডের সাথে যোগাযোগ করতে পারে না।

6. TensorFlow সার্ভিং সহ একটি পাঠ্য-শ্রেণীবিভাগ মডেল স্থাপন করুন

পাঠ্য শ্রেণিবিন্যাস একটি খুব সাধারণ মেশিন লার্নিং কাজ যা পাঠ্যগুলিকে পূর্বনির্ধারিত বিভাগে শ্রেণীবদ্ধ করে। এই কোডল্যাবে, আপনি টেনসরফ্লো সার্ভিং সহ TensorFlow Lite Model Maker কোডল্যাবের সাথে একটি মন্তব্য-স্প্যাম শনাক্তকরণ মডেলকে ট্রেন থেকে পূর্বপ্রশিক্ষিত মডেল স্থাপন করুন এবং ইনপুট পাঠ্যটিকে স্প্যাম বা স্প্যাম হিসাবে শ্রেণীবদ্ধ করতে আপনার ফ্লাটার ফ্রন্টএন্ড থেকে ব্যাকএন্ডে কল করুন।

TensorFlow পরিবেশন শুরু করুন

  • আপনার টার্মিনালে, ডকারের সাথে TensorFlow পরিবেশন শুরু করুন, কিন্তু আপনার কম্পিউটারে mm_spam_savedmodel ফোল্ডারের পরম পাথ দিয়ে PATH/TO/SAVEDMODEL স্থানধারকটিকে প্রতিস্থাপন করুন।
docker pull tensorflow/serving

docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/spam-detection" -e MODEL_NAME=spam-detection tensorflow/serving

ডকার স্বয়ংক্রিয়ভাবে টেনসরফ্লো সার্ভিং ইমেজটি প্রথমে ডাউনলোড করে, যা এক মিনিট সময় নেয়। এর পরে, টেনসরফ্লো পরিবেশন শুরু করা উচিত। লগটি এই কোড স্নিপেটের মতো হওয়া উচিত:

2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle.
2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz
2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/ssd_mobilenet_v2_2/123
2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds.
2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/ssd_mobilenet_v2_2/123/assets.extra/tf_serving_warmup_requests
2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: spam-detection version: 123}
2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models
2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled
2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 245] NET_LOG: Entering the event loop ...

7. ইনপুট বাক্যকে টোকেনাইজ করুন

ব্যাকএন্ড এখন প্রস্তুত, তাই আপনি টেনসরফ্লো সার্ভিং-এ ক্লায়েন্টের অনুরোধ পাঠাতে প্রায় প্রস্তুত, কিন্তু প্রথমে আপনাকে ইনপুট বাক্যটিকে টোকেনাইজ করতে হবে। আপনি যদি মডেলের ইনপুট টেনসর পরিদর্শন করেন, আপনি দেখতে পাবেন যে এটি কাঁচা স্ট্রিংয়ের পরিবর্তে 20টি পূর্ণসংখ্যার একটি তালিকা আশা করে। টোকেনাইজেশন হল যখন আপনি অ্যাপে টাইপ করা পৃথক শব্দগুলিকে শ্রেণীবিভাগের জন্য ব্যাকএন্ডে পাঠানোর আগে একটি শব্দকোষ অভিধানের ভিত্তিতে পূর্ণসংখ্যার তালিকায় ম্যাপ করেন। উদাহরণস্বরূপ, আপনি যদি buy book online to learn more টাইপ করেন, টোকেনাইজেশন প্রক্রিয়া এটিকে [32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] তে ম্যাপ করে। [32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] । শব্দভান্ডার অভিধানের উপর ভিত্তি করে নির্দিষ্ট সংখ্যা পরিবর্তিত হতে পারে।

  1. lib/main.dart ফাইলে, _vocabMap শব্দভাণ্ডার অভিধান তৈরি করতে predict() পদ্ধতিতে এই কোডটি যোগ করুন।
// Build _vocabMap if empty.
if (_vocabMap.isEmpty) {
  final vocabFileString = await rootBundle.loadString(vocabFile);
  final lines = vocabFileString.split('\n');
  for (final l in lines) {
    if (l != "") {
      var wordAndIndex = l.split(' ');
      (_vocabMap)[wordAndIndex[0]] = int.parse(wordAndIndex[1]);
    }
  }
} 
  1. পূর্ববর্তী কোড স্নিপেটের পরপরই, টোকেনাইজেশন বাস্তবায়ন করতে এই কোডটি যোগ করুন:
// Tokenize the input sentence.
final inputWords = _inputSentenceController.text
    .toLowerCase()
    .replaceAll(RegExp('[^a-z ]'), '')
    .split(' ');
// Initialize with padding token.
_tokenIndices = List.filled(maxSentenceLength, 0);
var i = 0;
for (final w in inputWords) {
  if ((_vocabMap).containsKey(w)) {
    _tokenIndices[i] = (_vocabMap)[w]!;
    i++;
  }

  // Truncate the string if longer than maxSentenceLength.
  if (i >= maxSentenceLength - 1) {
    break;
  }
}

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

8. REST এর মাধ্যমে TensorFlow সার্ভিংয়ের সাথে Flutter অ্যাপটি সংযুক্ত করুন

TensorFlow সার্ভিং-এ অনুরোধ পাঠানোর দুটি উপায় আছে:

  • বিশ্রাম
  • gRPC

অনুরোধ পাঠান এবং REST এর মাধ্যমে প্রতিক্রিয়া পান

REST এর মাধ্যমে অনুরোধ পাঠানো এবং প্রতিক্রিয়া পাওয়ার জন্য তিনটি সহজ ধাপ রয়েছে:

  1. REST অনুরোধ তৈরি করুন।
  2. TensorFlow সার্ভিং-এ REST অনুরোধ পাঠান।
  3. REST প্রতিক্রিয়া থেকে পূর্বাভাসিত ফলাফল বের করুন এবং UI রেন্ডার করুন।

আপনি main.dart ফাইলে এই ধাপগুলি সম্পূর্ণ করুন।

টেনসরফ্লো সার্ভিং-এ REST অনুরোধ তৈরি করুন এবং পাঠান

  1. এই মুহূর্তে, predict() ফাংশন TensorFlow Serving-এ REST অনুরোধ পাঠায় না। একটি REST অনুরোধ তৈরি করতে আপনাকে REST শাখা বাস্তবায়ন করতে হবে:
if (_connectionMode == ConnectionModeType.rest) {
  // TODO: Create and send the REST request.

}
  1. REST শাখায় এই কোড যোগ করুন:
//Create the REST request.
final response = await http.post(
  Uri.parse('http://' +
      _server +
      ':' +
      restPort.toString() +
      '/v1/models/' +
      modelName +
      ':predict'),
  body: jsonEncode(<String, List<List<int>>>{
    'instances': [_tokenIndices],
  }),
);

TensorFlow সার্ভিং থেকে REST প্রতিক্রিয়া প্রক্রিয়া করুন

  • REST প্রতিক্রিয়া পরিচালনা করতে আগের কোড স্নিপেটের ঠিক পরে এই কোডটি যোগ করুন:
// Process the REST response.
if (response.statusCode == 200) {
  Map<String, dynamic> result = jsonDecode(response.body);
  if (result['predictions']![0][1] >= classificationThreshold) {
    return 'This sentence is spam. Spam score is ' +
        result['predictions']![0][1].toString();
  }
  return 'This sentence is not spam. Spam score is ' +
      result['predictions']![0][1].toString();
} else {
  throw Exception('Error response');
}

পোস্টপ্রসেসিং কোডটি সম্ভাব্যতা বের করে যে ইনপুট বাক্যটি প্রতিক্রিয়া থেকে একটি স্প্যাম বার্তা এবং UI-তে শ্রেণিবিন্যাস ফলাফল প্রদর্শন করে।

চালাও এটা

  1. ক্লিক a19a0c68bc4046e6.png ডিবাগিং শুরু করুন এবং তারপর অ্যাপটি লোড হওয়ার জন্য অপেক্ষা করুন।
  2. কিছু পাঠ্য লিখুন এবং তারপরে REST > শ্রেণিবদ্ধ করুন নির্বাচন করুন।

8e21d795af36d07a.pnge79a0367a03c2169.png

9. GRPC-এর মাধ্যমে TensorFlow সার্ভিংয়ের সাথে Flutter অ্যাপটি সংযুক্ত করুন

REST ছাড়াও, TensorFlow সার্ভিং এছাড়াও gRPC সমর্থন করে।

b6f4449c2c850b0e.png

gRPC হল একটি আধুনিক, ওপেন সোর্স, হাই-পারফরম্যান্স রিমোট প্রসিডিউর কল (RPC) ফ্রেমওয়ার্ক যা যেকোনো পরিবেশে চলতে পারে। এটি লোড ব্যালেন্সিং, ট্রেসিং, হেলথ চেকিং এবং প্রমাণীকরণের জন্য প্লাগেবল সাপোর্ট সহ ডেটা সেন্টারগুলিতে এবং জুড়ে পরিষেবাগুলিকে দক্ষতার সাথে সংযুক্ত করতে পারে। এটা দেখা গেছে যে জিআরপিসি অনুশীলনে REST এর চেয়ে বেশি কার্যকরী।

অনুরোধ পাঠান এবং gRPC এর সাথে প্রতিক্রিয়া পান

জিআরপিসি-তে অনুরোধ পাঠানো এবং প্রতিক্রিয়া পাওয়ার জন্য চারটি সহজ ধাপ রয়েছে:

  1. ঐচ্ছিক: gRPC ক্লায়েন্ট স্টাব কোড তৈরি করুন।
  2. জিআরপিসি অনুরোধ তৈরি করুন।
  3. টেনসরফ্লো সার্ভিং-এ gRPC অনুরোধ পাঠান।
  4. gRPC প্রতিক্রিয়া থেকে পূর্বাভাসিত ফলাফল বের করুন এবং UI রেন্ডার করুন।

আপনি main.dart ফাইলে এই ধাপগুলি সম্পূর্ণ করুন।

ঐচ্ছিক: gRPC ক্লায়েন্ট স্টাব কোড তৈরি করুন

TensorFlow সার্ভিংয়ের সাথে gRPC ব্যবহার করতে, আপনাকে gRPC ওয়ার্কফ্লো অনুসরণ করতে হবে। বিস্তারিত সম্পর্কে আরও জানতে, gRPC ডকুমেন্টেশন দেখুন।

a9d0e5cb543467b4.png

TensorFlow সার্ভিং এবং TensorFlow আপনার জন্য .proto ফাইলগুলিকে সংজ্ঞায়িত করে৷ TensorFlow এবং TensorFlow সার্ভিং 2.8 অনুযায়ী, এই .proto ফাইলগুলি প্রয়োজন:

tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto

tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto

google/protobuf/any.proto
google/protobuf/wrappers.proto
  • আপনার টার্মিনালে, starter/lib/proto/ ফোল্ডারে নেভিগেট করুন এবং stub তৈরি করুন:
bash generate_grpc_stub_dart.sh

জিআরপিসি অনুরোধ তৈরি করুন

REST অনুরোধের মতো, আপনি gRPC শাখায় gRPC অনুরোধ তৈরি করেন।

if (_connectionMode == ConnectionModeType.rest) {

} else {
  // TODO: Create and send the gRPC request.

}
  • gRPC অনুরোধ তৈরি করতে এই কোড যোগ করুন:
//Create the gRPC request.
final channel = ClientChannel(_server,
    port: grpcPort,
    options:
        const ChannelOptions(credentials: ChannelCredentials.insecure()));
_stub = PredictionServiceClient(channel,
    options: CallOptions(timeout: const Duration(seconds: 10)));

ModelSpec modelSpec = ModelSpec(
  name: 'spam-detection',
  signatureName: 'serving_default',
);

TensorShapeProto_Dim batchDim = TensorShapeProto_Dim(size: Int64(1));
TensorShapeProto_Dim inputDim =
    TensorShapeProto_Dim(size: Int64(maxSentenceLength));
TensorShapeProto inputTensorShape =
    TensorShapeProto(dim: [batchDim, inputDim]);
TensorProto inputTensor = TensorProto(
    dtype: DataType.DT_INT32,
    tensorShape: inputTensorShape,
    intVal: _tokenIndices);

// If you train your own model, update the input and output tensor names.
const inputTensorName = 'input_3';
const outputTensorName = 'dense_5';
PredictRequest request = PredictRequest(
    modelSpec: modelSpec, inputs: {inputTensorName: inputTensor});

দ্রষ্টব্য: মডেল আর্কিটেকচার একই হলেও ইনপুট এবং আউটপুট টেনসর নামগুলি মডেল থেকে মডেলে আলাদা হতে পারে। আপনি যদি আপনার নিজের মডেলকে প্রশিক্ষণ দেন তবে সেগুলি আপডেট করতে ভুলবেন না।

টেনসরফ্লো সার্ভিং-এ gRPC অনুরোধ পাঠান

  • টেনসরফ্লো সার্ভিং-এ gRPC অনুরোধ পাঠাতে আগের কোড স্নিপেটের পরে এই কোডটি যোগ করুন:
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);

TensorFlow সার্ভিং থেকে gRPC প্রতিক্রিয়া প্রক্রিয়া করুন

  • প্রতিক্রিয়া পরিচালনা করতে কলব্যাক ফাংশনগুলি বাস্তবায়ন করতে পূর্ববর্তী কোড স্নিপেটের পরে এই কোডটি যুক্ত করুন:
// Process the response.
if (response.outputs.containsKey(outputTensorName)) {
  if (response.outputs[outputTensorName]!.floatVal[1] >
      classificationThreshold) {
    return 'This sentence is spam. Spam score is ' +
        response.outputs[outputTensorName]!.floatVal[1].toString();
  } else {
    return 'This sentence is not spam. Spam score is ' +
        response.outputs[outputTensorName]!.floatVal[1].toString();
  }
} else {
  throw Exception('Error response');
}

এখন পোস্টপ্রসেসিং কোড প্রতিক্রিয়া থেকে শ্রেণীবিভাগের ফলাফল বের করে এবং এটি UI এ প্রদর্শন করে।

চালাও এটা

  1. ক্লিক a19a0c68bc4046e6.png ডিবাগিং শুরু করুন এবং তারপর অ্যাপটি লোড হওয়ার জন্য অপেক্ষা করুন।
  2. কিছু পাঠ্য লিখুন এবং তারপর gRPC > শ্রেণীবদ্ধ নির্বাচন করুন।

e44e6e9a5bde2188.png92644d723f61968c.png

10. অভিনন্দন

আপনি আপনার অ্যাপে টেক্সট-শ্রেণীবিভাগের ক্ষমতা যোগ করতে TensorFlow সার্ভিং ব্যবহার করেছেন!

পরবর্তী কোডল্যাবে, আপনি মডেলটিকে উন্নত করবেন যাতে আপনি নির্দিষ্ট স্প্যাম বার্তা সনাক্ত করতে পারেন যা বর্তমান অ্যাপ দ্বারা সনাক্ত করা যায় না।

আরও জানুন