image_classification.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. from typing import Any, Union, overload
  15. import numpy as np
  16. from ..utils import (
  17. ExplicitEnum,
  18. add_end_docstrings,
  19. is_tf_available,
  20. is_torch_available,
  21. is_vision_available,
  22. logging,
  23. requires_backends,
  24. )
  25. from .base import Pipeline, build_pipeline_init_args
  26. if is_vision_available():
  27. from PIL import Image
  28. from ..image_utils import load_image
  29. if is_tf_available():
  30. from ..models.auto.modeling_tf_auto import TF_MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  31. if is_torch_available():
  32. import torch
  33. from ..models.auto.modeling_auto import MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  34. logger = logging.get_logger(__name__)
  35. # Copied from transformers.pipelines.text_classification.sigmoid
  36. def sigmoid(_outputs):
  37. return 1.0 / (1.0 + np.exp(-_outputs))
  38. # Copied from transformers.pipelines.text_classification.softmax
  39. def softmax(_outputs):
  40. maxes = np.max(_outputs, axis=-1, keepdims=True)
  41. shifted_exp = np.exp(_outputs - maxes)
  42. return shifted_exp / shifted_exp.sum(axis=-1, keepdims=True)
  43. # Copied from transformers.pipelines.text_classification.ClassificationFunction
  44. class ClassificationFunction(ExplicitEnum):
  45. SIGMOID = "sigmoid"
  46. SOFTMAX = "softmax"
  47. NONE = "none"
  48. @add_end_docstrings(
  49. build_pipeline_init_args(has_image_processor=True),
  50. r"""
  51. function_to_apply (`str`, *optional*, defaults to `"default"`):
  52. The function to apply to the model outputs in order to retrieve the scores. Accepts four different values:
  53. - `"default"`: if the model has a single label, will apply the sigmoid function on the output. If the model
  54. has several labels, will apply the softmax function on the output.
  55. - `"sigmoid"`: Applies the sigmoid function on the output.
  56. - `"softmax"`: Applies the softmax function on the output.
  57. - `"none"`: Does not apply any function on the output.""",
  58. )
  59. class ImageClassificationPipeline(Pipeline):
  60. """
  61. Image classification pipeline using any `AutoModelForImageClassification`. This pipeline predicts the class of an
  62. image.
  63. Example:
  64. ```python
  65. >>> from transformers import pipeline
  66. >>> classifier = pipeline(model="microsoft/beit-base-patch16-224-pt22k-ft22k")
  67. >>> classifier("https://huggingface.co/datasets/Narsil/image_dummy/raw/main/parrots.png")
  68. [{'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'}]
  69. ```
  70. Learn more about the basics of using a pipeline in the [pipeline tutorial](../pipeline_tutorial)
  71. This image classification pipeline can currently be loaded from [`pipeline`] using the following task identifier:
  72. `"image-classification"`.
  73. See the list of available models on
  74. [huggingface.co/models](https://huggingface.co/models?filter=image-classification).
  75. """
  76. function_to_apply: ClassificationFunction = ClassificationFunction.NONE
  77. _load_processor = False
  78. _load_image_processor = True
  79. _load_feature_extractor = False
  80. _load_tokenizer = False
  81. def __init__(self, *args, **kwargs):
  82. super().__init__(*args, **kwargs)
  83. requires_backends(self, "vision")
  84. self.check_model_type(
  85. TF_MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  86. if self.framework == "tf"
  87. else MODEL_FOR_IMAGE_CLASSIFICATION_MAPPING_NAMES
  88. )
  89. def _sanitize_parameters(self, top_k=None, function_to_apply=None, timeout=None):
  90. preprocess_params = {}
  91. if timeout is not None:
  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. @overload
  102. def __call__(self, inputs: Union[str, "Image.Image"], **kwargs: Any) -> list[dict[str, Any]]: ...
  103. @overload
  104. def __call__(self, inputs: Union[list[str], list["Image.Image"]], **kwargs: Any) -> list[list[dict[str, Any]]]: ...
  105. def __call__(
  106. self, inputs: Union[str, list[str], "Image.Image", list["Image.Image"]], **kwargs: Any
  107. ) -> Union[list[dict[str, Any]], list[list[dict[str, Any]]]]:
  108. """
  109. Assign labels to the image(s) passed as inputs.
  110. Args:
  111. inputs (`str`, `list[str]`, `PIL.Image` or `list[PIL.Image]`):
  112. The pipeline handles three types of images:
  113. - A string containing a http link pointing to an image
  114. - A string containing a local path to an image
  115. - An image loaded in PIL directly
  116. The pipeline accepts either a single image or a batch of images, which must then be passed as a string.
  117. Images in a batch must all be in the same format: all as http links, all as local paths, or all as PIL
  118. images.
  119. function_to_apply (`str`, *optional*, defaults to `"default"`):
  120. The function to apply to the model outputs in order to retrieve the scores. Accepts four different
  121. values:
  122. If this argument is not specified, then it will apply the following functions according to the number
  123. of labels:
  124. - If the model has a single label, will apply the sigmoid function on the output.
  125. - If the model has several labels, will apply the softmax function on the output.
  126. Possible values are:
  127. - `"sigmoid"`: Applies the sigmoid function on the output.
  128. - `"softmax"`: Applies the softmax function on the output.
  129. - `"none"`: Does not apply any function on the output.
  130. top_k (`int`, *optional*, defaults to 5):
  131. The number of top labels that will be returned by the pipeline. If the provided number is higher than
  132. the number of labels available in the model configuration, it will default to the number of labels.
  133. timeout (`float`, *optional*, defaults to None):
  134. The maximum time in seconds to wait for fetching images from the web. If None, no timeout is set and
  135. the call may block forever.
  136. Return:
  137. A dictionary or a list of dictionaries containing result. If the input is a single image, will return a
  138. dictionary, if the input is a list of several images, will return a list of dictionaries corresponding to
  139. the images.
  140. The dictionaries contain the following keys:
  141. - **label** (`str`) -- The label identified by the model.
  142. - **score** (`int`) -- The score attributed by the model for that label.
  143. """
  144. # After deprecation of this is completed, remove the default `None` value for `images`
  145. if "images" in kwargs:
  146. inputs = kwargs.pop("images")
  147. if inputs is None:
  148. raise ValueError("Cannot call the image-classification pipeline without an inputs argument!")
  149. return super().__call__(inputs, **kwargs)
  150. def preprocess(self, image, timeout=None):
  151. image = load_image(image, timeout=timeout)
  152. model_inputs = self.image_processor(images=image, return_tensors=self.framework)
  153. if self.framework == "pt":
  154. model_inputs = model_inputs.to(self.dtype)
  155. return model_inputs
  156. def _forward(self, model_inputs):
  157. model_outputs = self.model(**model_inputs)
  158. return model_outputs
  159. def postprocess(self, model_outputs, function_to_apply=None, top_k=5):
  160. if function_to_apply is None:
  161. if self.model.config.problem_type == "multi_label_classification" or self.model.config.num_labels == 1:
  162. function_to_apply = ClassificationFunction.SIGMOID
  163. elif self.model.config.problem_type == "single_label_classification" or self.model.config.num_labels > 1:
  164. function_to_apply = ClassificationFunction.SOFTMAX
  165. elif hasattr(self.model.config, "function_to_apply") and function_to_apply is None:
  166. function_to_apply = self.model.config.function_to_apply
  167. else:
  168. function_to_apply = ClassificationFunction.NONE
  169. if top_k > self.model.config.num_labels:
  170. top_k = self.model.config.num_labels
  171. outputs = model_outputs["logits"][0]
  172. if self.framework == "pt" and outputs.dtype in (torch.bfloat16, torch.float16):
  173. outputs = outputs.to(torch.float32).numpy()
  174. else:
  175. outputs = outputs.numpy()
  176. if function_to_apply == ClassificationFunction.SIGMOID:
  177. scores = sigmoid(outputs)
  178. elif function_to_apply == ClassificationFunction.SOFTMAX:
  179. scores = softmax(outputs)
  180. elif function_to_apply == ClassificationFunction.NONE:
  181. scores = outputs
  182. else:
  183. raise ValueError(f"Unrecognized `function_to_apply` argument: {function_to_apply}")
  184. dict_scores = [
  185. {"label": self.model.config.id2label[i], "score": score.item()} for i, score in enumerate(scores)
  186. ]
  187. dict_scores.sort(key=lambda x: x["score"], reverse=True)
  188. if top_k is not None:
  189. dict_scores = dict_scores[:top_k]
  190. return dict_scores