Sync video classification pipeline with huggingface_hub spec (#34288)

* Sync video classification pipeline

* Add disclaimer
This commit is contained in:
Matt
2024-10-22 13:33:49 +01:00
committed by GitHub
parent 93352e81f5
commit 681fc43713
3 changed files with 61 additions and 7 deletions

View File

@@ -1,3 +1,17 @@
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import warnings
from io import BytesIO
from typing import List, Union
@@ -42,7 +56,7 @@ class VideoClassificationPipeline(Pipeline):
requires_backends(self, "av")
self.check_model_type(MODEL_FOR_VIDEO_CLASSIFICATION_MAPPING_NAMES)
def _sanitize_parameters(self, top_k=None, num_frames=None, frame_sampling_rate=None):
def _sanitize_parameters(self, top_k=None, num_frames=None, frame_sampling_rate=None, function_to_apply=None):
preprocess_params = {}
if frame_sampling_rate is not None:
preprocess_params["frame_sampling_rate"] = frame_sampling_rate
@@ -52,14 +66,23 @@ class VideoClassificationPipeline(Pipeline):
postprocess_params = {}
if top_k is not None:
postprocess_params["top_k"] = top_k
if function_to_apply is not None:
if function_to_apply not in ["softmax", "sigmoid", "none"]:
raise ValueError(
f"Invalid value for `function_to_apply`: {function_to_apply}. "
"Valid options are ['softmax', 'sigmoid', 'none']"
)
postprocess_params["function_to_apply"] = function_to_apply
else:
postprocess_params["function_to_apply"] = "softmax"
return preprocess_params, {}, postprocess_params
def __call__(self, videos: Union[str, List[str]], **kwargs):
def __call__(self, inputs: Union[str, List[str]] = None, **kwargs):
"""
Assign labels to the video(s) passed as inputs.
Args:
videos (`str`, `List[str]`):
inputs (`str`, `List[str]`):
The pipeline handles three types of videos:
- A string containing a http link pointing to a video
@@ -76,6 +99,11 @@ class VideoClassificationPipeline(Pipeline):
frame_sampling_rate (`int`, *optional*, defaults to 1):
The sampling rate used to select frames from the video. If not provided, will default to 1, i.e. every
frame will be used.
function_to_apply(`str`, *optional*, defaults to "softmax"):
The function to apply to the model output. By default, the pipeline will apply the softmax function to
the output of the model. Valid options: ["softmax", "sigmoid", "none"]. Note that passing Python's
built-in `None` will default to "softmax", so you need to pass the string "none" to disable any
post-processing.
Return:
A dictionary or a list of dictionaries containing result. If the input is a single video, will return a
@@ -87,7 +115,16 @@ class VideoClassificationPipeline(Pipeline):
- **label** (`str`) -- The label identified by the model.
- **score** (`int`) -- The score attributed by the model for that label.
"""
return super().__call__(videos, **kwargs)
# After deprecation of this is completed, remove the default `None` value for `images`
if "videos" in kwargs:
warnings.warn(
"The `videos` argument has been renamed to `inputs`. In version 5 of Transformers, `videos` will no longer be accepted",
FutureWarning,
)
inputs = kwargs.pop("videos")
if inputs is None:
raise ValueError("Cannot call the video-classification pipeline without an inputs argument!")
return super().__call__(inputs, **kwargs)
def preprocess(self, video, num_frames=None, frame_sampling_rate=1):
if num_frames is None:
@@ -114,12 +151,17 @@ class VideoClassificationPipeline(Pipeline):
model_outputs = self.model(**model_inputs)
return model_outputs
def postprocess(self, model_outputs, top_k=5):
def postprocess(self, model_outputs, top_k=5, function_to_apply="softmax"):
if top_k > self.model.config.num_labels:
top_k = self.model.config.num_labels
if self.framework == "pt":
probs = model_outputs.logits.softmax(-1)[0]
if function_to_apply == "softmax":
probs = model_outputs.logits[0].softmax(-1)
elif function_to_apply == "sigmoid":
probs = model_outputs.logits[0].sigmoid()
else:
probs = model_outputs.logits[0]
scores, ids = probs.topk(top_k)
else:
raise ValueError(f"Unsupported framework: {self.framework}")

View File

@@ -14,11 +14,12 @@
import unittest
from huggingface_hub import hf_hub_download
from huggingface_hub import VideoClassificationOutputElement, hf_hub_download
from transformers import MODEL_FOR_VIDEO_CLASSIFICATION_MAPPING, VideoMAEFeatureExtractor
from transformers.pipelines import VideoClassificationPipeline, pipeline
from transformers.testing_utils import (
compare_pipeline_output_to_hub_spec,
is_pipeline_test,
nested_simplify,
require_av,
@@ -76,6 +77,8 @@ class VideoClassificationPipelineTests(unittest.TestCase):
{"score": ANY(float), "label": ANY(str)},
],
)
for element in outputs:
compare_pipeline_output_to_hub_spec(element, VideoClassificationOutputElement)
@require_torch
def test_small_model_pt(self):
@@ -93,6 +96,9 @@ class VideoClassificationPipelineTests(unittest.TestCase):
nested_simplify(outputs, decimals=4),
[{"score": 0.5199, "label": "LABEL_0"}, {"score": 0.4801, "label": "LABEL_1"}],
)
for output in outputs:
for element in output:
compare_pipeline_output_to_hub_spec(element, VideoClassificationOutputElement)
outputs = video_classifier(
[
@@ -108,6 +114,9 @@ class VideoClassificationPipelineTests(unittest.TestCase):
[{"score": 0.5199, "label": "LABEL_0"}, {"score": 0.4801, "label": "LABEL_1"}],
],
)
for output in outputs:
for element in output:
compare_pipeline_output_to_hub_spec(element, VideoClassificationOutputElement)
@require_tf
@unittest.skip

View File

@@ -34,6 +34,7 @@ from huggingface_hub import (
ImageToTextInput,
ObjectDetectionInput,
QuestionAnsweringInput,
VideoClassificationInput,
ZeroShotImageClassificationInput,
)
@@ -47,6 +48,7 @@ from transformers.pipelines import (
ImageToTextPipeline,
ObjectDetectionPipeline,
QuestionAnsweringPipeline,
VideoClassificationPipeline,
ZeroShotImageClassificationPipeline,
)
from transformers.testing_utils import (
@@ -132,6 +134,7 @@ task_to_pipeline_and_spec_mapping = {
"image-to-text": (ImageToTextPipeline, ImageToTextInput),
"object-detection": (ObjectDetectionPipeline, ObjectDetectionInput),
"question-answering": (QuestionAnsweringPipeline, QuestionAnsweringInput),
"video-classification": (VideoClassificationPipeline, VideoClassificationInput),
"zero-shot-image-classification": (ZeroShotImageClassificationPipeline, ZeroShotImageClassificationInput),
}