image_classification.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # Copyright 2023 The HuggingFace Team. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import warnings
  15. from typing import List, Union
  16. import numpy as np
  17. from ..utils import (
  18. ExplicitEnum,
  19. add_end_docstrings,
  20. is_tf_available,
  21. is_torch_available,
  22. is_vision_available,
  23. logging,
  24. requires_backends,
  25. )
  26. from .base import Pipeline, build_pipeline_init_args
  27. if is_vision_available():
  28. from PIL import Image
  29. from ..image_utils import load_image
  30. if is_tf_available():
  31. from ..models.auto.modeling_tf_auto import TF_MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  32. if is_torch_available():
  33. import torch
  34. from ..models.auto.modeling_auto import MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  35. logger = logging.get_logger(__name__)
  36. # Copied from transformers.pipelines.text_classification.sigmoid
  37. def sigmoid(_outputs):
  38. return 1.0 / (1.0 + np.exp(-_outputs))
  39. # Copied from transformers.pipelines.text_classification.softmax
  40. def softmax(_outputs):
  41. maxes = np.max(_outputs, axis=-1, keepdims=True)
  42. shifted_exp = np.exp(_outputs - maxes)
  43. return shifted_exp / shifted_exp.sum(axis=-1, keepdims=True)
  44. # Copied from transformers.pipelines.text_classification.ClassificationFunction
  45. class ClassificationFunction(ExplicitEnum):
  46. SIGMOID = "sigmoid"
  47. SOFTMAX = "softmax"
  48. NONE = "none"
  49. @add_end_docstrings(
  50. build_pipeline_init_args(has_image_processor=True),
  51. r"""
  52. function_to_apply (`str`, *optional*, defaults to `"default"`):
  53. The function to apply to the model outputs in order to retrieve the scores. Accepts four different values:
  54. - `"default"`: if the model has a single label, will apply the sigmoid function on the output. If the model
  55. has several labels, will apply the softmax function on the output.
  56. - `"sigmoid"`: Applies the sigmoid function on the output.
  57. - `"softmax"`: Applies the softmax function on the output.
  58. - `"none"`: Does not apply any function on the output.""",
  59. )
  60. class ImageClassificationPipeline(Pipeline):
  61. """
  62. Image classification pipeline using any `AutoModelForImageClassification`. This pipeline predicts the class of an
  63. image.
  64. Example:
  65. ```python
  66. >>> from transformers import pipeline
  67. >>> classifier = pipeline(model="microsoft/beit-base-patch16-224-pt22k-ft22k")
  68. >>> classifier("https://huggingface.co/datasets/Narsil/image_dummy/raw/main/parrots.png")
  69. [{'score': 0.442, 'label': 'macaw'}, {'score': 0.088, 'label': 'popinjay'}, {'score': 0.075, 'label': 'parrot'}, {'score': 0.073, 'label': 'parodist, lampooner'}, {'score': 0.046, 'label': 'poll, poll_parrot'}]
  70. ```
  71. Learn more about the basics of using a pipeline in the [pipeline tutorial](../pipeline_tutorial)
  72. This image classification pipeline can currently be loaded from [`pipeline`] using the following task identifier:
  73. `"image-classification"`.
  74. See the list of available models on
  75. [huggingface.co/models](https://huggingface.co/models?filter=image-classification).
  76. """
  77. function_to_apply: ClassificationFunction = ClassificationFunction.NONE
  78. def __init__(self, *args, **kwargs):
  79. super().__init__(*args, **kwargs)
  80. requires_backends(self, "vision")
  81. self.check_model_type(
  82. TF_MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  83. if self.framework == "tf"
  84. else MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  85. )
  86. def _sanitize_parameters(self, top_k=None, function_to_apply=None, timeout=None):
  87. preprocess_params = {}
  88. if timeout is not None:
  89. warnings.warn(
  90. "The `timeout` argument is deprecated and will be removed in version 5 of Transformers", FutureWarning
  91. )
  92. preprocess_params["timeout"] = timeout
  93. postprocess_params = {}
  94. if top_k is not None:
  95. postprocess_params["top_k"] = top_k
  96. if isinstance(function_to_apply, str):
  97. function_to_apply = ClassificationFunction(function_to_apply.lower())
  98. if function_to_apply is not None:
  99. postprocess_params["function_to_apply"] = function_to_apply
  100. return preprocess_params, {}, postprocess_params
  101. def __call__(self, inputs: Union[str, List[str], "Image.Image", List["Image.Image"]] = None, **kwargs):
  102. """
  103. Assign labels to the image(s) passed as inputs.
  104. Args:
  105. inputs (`str`, `List[str]`, `PIL.Image` or `List[PIL.Image]`):
  106. The pipeline handles three types of images:
  107. - A string containing a http link pointing to an image
  108. - A string containing a local path to an image
  109. - An image loaded in PIL directly
  110. The pipeline accepts either a single image or a batch of images, which must then be passed as a string.
  111. Images in a batch must all be in the same format: all as http links, all as local paths, or all as PIL
  112. images.
  113. function_to_apply (`str`, *optional*, defaults to `"default"`):
  114. The function to apply to the model outputs in order to retrieve the scores. Accepts four different
  115. values:
  116. If this argument is not specified, then it will apply the following functions according to the number
  117. of labels:
  118. - If the model has a single label, will apply the sigmoid function on the output.
  119. - If the model has several labels, will apply the softmax function on the output.
  120. Possible values are:
  121. - `"sigmoid"`: Applies the sigmoid function on the output.
  122. - `"softmax"`: Applies the softmax function on the output.
  123. - `"none"`: Does not apply any function on the output.
  124. top_k (`int`, *optional*, defaults to 5):
  125. The number of top labels that will be returned by the pipeline. If the provided number is higher than
  126. the number of labels available in the model configuration, it will default to the number of labels.
  127. Return:
  128. A dictionary or a list of dictionaries containing result. If the input is a single image, will return a
  129. dictionary, if the input is a list of several images, will return a list of dictionaries corresponding to
  130. the images.
  131. The dictionaries contain the following keys:
  132. - **label** (`str`) -- The label identified by the model.
  133. - **score** (`int`) -- The score attributed by the model for that label.
  134. """
  135. # After deprecation of this is completed, remove the default `None` value for `images`
  136. if "images" in kwargs:
  137. inputs = kwargs.pop("images")
  138. if inputs is None:
  139. raise ValueError("Cannot call the image-classification pipeline without an inputs argument!")
  140. return super().__call__(inputs, **kwargs)
  141. def preprocess(self, image, timeout=None):
  142. image = load_image(image, timeout=timeout)
  143. model_inputs = self.image_processor(images=image, return_tensors=self.framework)
  144. if self.framework == "pt":
  145. model_inputs = model_inputs.to(self.torch_dtype)
  146. return model_inputs
  147. def _forward(self, model_inputs):
  148. model_outputs = self.model(**model_inputs)
  149. return model_outputs
  150. def postprocess(self, model_outputs, function_to_apply=None, top_k=5):
  151. if function_to_apply is None:
  152. if self.model.config.problem_type == "single_label_classification" or self.model.config.num_labels == 1:
  153. function_to_apply = ClassificationFunction.SIGMOID
  154. elif self.model.config.problem_type == "multi_label_classification" or self.model.config.num_labels > 1:
  155. function_to_apply = ClassificationFunction.SOFTMAX
  156. elif hasattr(self.model.config, "function_to_apply") and function_to_apply is None:
  157. function_to_apply = self.model.config.function_to_apply
  158. else:
  159. function_to_apply = ClassificationFunction.NONE
  160. if top_k > self.model.config.num_labels:
  161. top_k = self.model.config.num_labels
  162. outputs = model_outputs["logits"][0]
  163. if self.framework == "pt" and outputs.dtype in (torch.bfloat16, torch.float16):
  164. outputs = outputs.to(torch.float32).numpy()
  165. else:
  166. outputs = outputs.numpy()
  167. if function_to_apply == ClassificationFunction.SIGMOID:
  168. scores = sigmoid(outputs)
  169. elif function_to_apply == ClassificationFunction.SOFTMAX:
  170. scores = softmax(outputs)
  171. elif function_to_apply == ClassificationFunction.NONE:
  172. scores = outputs
  173. else:
  174. raise ValueError(f"Unrecognized `function_to_apply` argument: {function_to_apply}")
  175. dict_scores = [
  176. {"label": self.model.config.id2label[i], "score": score.item()} for i, score in enumerate(scores)
  177. ]
  178. dict_scores.sort(key=lambda x: x["score"], reverse=True)
  179. if top_k is not None:
  180. dict_scores = dict_scores[:top_k]
  181. return dict_scores