image_classification.py 9.6 KB

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