s3d.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. from functools import partial
  2. from typing import Any, Callable, Optional
  3. import torch
  4. from torch import nn
  5. from torchvision.ops.misc import Conv3dNormActivation
  6. from ...transforms._presets import VideoClassification
  7. from ...utils import _log_api_usage_once
  8. from .._api import register_model, Weights, WeightsEnum
  9. from .._meta import _KINETICS400_CATEGORIES
  10. from .._utils import _ovewrite_named_param, handle_legacy_interface
  11. __all__ = [
  12. "S3D",
  13. "S3D_Weights",
  14. "s3d",
  15. ]
  16. class TemporalSeparableConv(nn.Sequential):
  17. def __init__(
  18. self,
  19. in_planes: int,
  20. out_planes: int,
  21. kernel_size: int,
  22. stride: int,
  23. padding: int,
  24. norm_layer: Callable[..., nn.Module],
  25. ):
  26. super().__init__(
  27. Conv3dNormActivation(
  28. in_planes,
  29. out_planes,
  30. kernel_size=(1, kernel_size, kernel_size),
  31. stride=(1, stride, stride),
  32. padding=(0, padding, padding),
  33. bias=False,
  34. norm_layer=norm_layer,
  35. ),
  36. Conv3dNormActivation(
  37. out_planes,
  38. out_planes,
  39. kernel_size=(kernel_size, 1, 1),
  40. stride=(stride, 1, 1),
  41. padding=(padding, 0, 0),
  42. bias=False,
  43. norm_layer=norm_layer,
  44. ),
  45. )
  46. class SepInceptionBlock3D(nn.Module):
  47. def __init__(
  48. self,
  49. in_planes: int,
  50. b0_out: int,
  51. b1_mid: int,
  52. b1_out: int,
  53. b2_mid: int,
  54. b2_out: int,
  55. b3_out: int,
  56. norm_layer: Callable[..., nn.Module],
  57. ):
  58. super().__init__()
  59. self.branch0 = Conv3dNormActivation(in_planes, b0_out, kernel_size=1, stride=1, norm_layer=norm_layer)
  60. self.branch1 = nn.Sequential(
  61. Conv3dNormActivation(in_planes, b1_mid, kernel_size=1, stride=1, norm_layer=norm_layer),
  62. TemporalSeparableConv(b1_mid, b1_out, kernel_size=3, stride=1, padding=1, norm_layer=norm_layer),
  63. )
  64. self.branch2 = nn.Sequential(
  65. Conv3dNormActivation(in_planes, b2_mid, kernel_size=1, stride=1, norm_layer=norm_layer),
  66. TemporalSeparableConv(b2_mid, b2_out, kernel_size=3, stride=1, padding=1, norm_layer=norm_layer),
  67. )
  68. self.branch3 = nn.Sequential(
  69. nn.MaxPool3d(kernel_size=(3, 3, 3), stride=1, padding=1),
  70. Conv3dNormActivation(in_planes, b3_out, kernel_size=1, stride=1, norm_layer=norm_layer),
  71. )
  72. def forward(self, x):
  73. x0 = self.branch0(x)
  74. x1 = self.branch1(x)
  75. x2 = self.branch2(x)
  76. x3 = self.branch3(x)
  77. out = torch.cat((x0, x1, x2, x3), 1)
  78. return out
  79. class S3D(nn.Module):
  80. """S3D main class.
  81. Args:
  82. num_class (int): number of classes for the classification task.
  83. dropout (float): dropout probability.
  84. norm_layer (Optional[Callable]): Module specifying the normalization layer to use.
  85. Inputs:
  86. x (Tensor): batch of videos with dimensions (batch, channel, time, height, width)
  87. """
  88. def __init__(
  89. self,
  90. num_classes: int = 400,
  91. dropout: float = 0.2,
  92. norm_layer: Optional[Callable[..., torch.nn.Module]] = None,
  93. ) -> None:
  94. super().__init__()
  95. _log_api_usage_once(self)
  96. if norm_layer is None:
  97. norm_layer = partial(nn.BatchNorm3d, eps=0.001, momentum=0.001)
  98. self.features = nn.Sequential(
  99. TemporalSeparableConv(3, 64, 7, 2, 3, norm_layer),
  100. nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2), padding=(0, 1, 1)),
  101. Conv3dNormActivation(
  102. 64,
  103. 64,
  104. kernel_size=1,
  105. stride=1,
  106. norm_layer=norm_layer,
  107. ),
  108. TemporalSeparableConv(64, 192, 3, 1, 1, norm_layer),
  109. nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2), padding=(0, 1, 1)),
  110. SepInceptionBlock3D(192, 64, 96, 128, 16, 32, 32, norm_layer),
  111. SepInceptionBlock3D(256, 128, 128, 192, 32, 96, 64, norm_layer),
  112. nn.MaxPool3d(kernel_size=(3, 3, 3), stride=(2, 2, 2), padding=(1, 1, 1)),
  113. SepInceptionBlock3D(480, 192, 96, 208, 16, 48, 64, norm_layer),
  114. SepInceptionBlock3D(512, 160, 112, 224, 24, 64, 64, norm_layer),
  115. SepInceptionBlock3D(512, 128, 128, 256, 24, 64, 64, norm_layer),
  116. SepInceptionBlock3D(512, 112, 144, 288, 32, 64, 64, norm_layer),
  117. SepInceptionBlock3D(528, 256, 160, 320, 32, 128, 128, norm_layer),
  118. nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2), padding=(0, 0, 0)),
  119. SepInceptionBlock3D(832, 256, 160, 320, 32, 128, 128, norm_layer),
  120. SepInceptionBlock3D(832, 384, 192, 384, 48, 128, 128, norm_layer),
  121. )
  122. self.avgpool = nn.AvgPool3d(kernel_size=(2, 7, 7), stride=1)
  123. self.classifier = nn.Sequential(
  124. nn.Dropout(p=dropout),
  125. nn.Conv3d(1024, num_classes, kernel_size=1, stride=1, bias=True),
  126. )
  127. def forward(self, x):
  128. x = self.features(x)
  129. x = self.avgpool(x)
  130. x = self.classifier(x)
  131. x = torch.mean(x, dim=(2, 3, 4))
  132. return x
  133. class S3D_Weights(WeightsEnum):
  134. KINETICS400_V1 = Weights(
  135. url="https://download.pytorch.org/models/s3d-d76dad2f.pth",
  136. transforms=partial(
  137. VideoClassification,
  138. crop_size=(224, 224),
  139. resize_size=(256, 256),
  140. ),
  141. meta={
  142. "min_size": (224, 224),
  143. "min_temporal_size": 14,
  144. "categories": _KINETICS400_CATEGORIES,
  145. "recipe": "https://github.com/pytorch/vision/tree/main/references/video_classification#s3d",
  146. "_docs": (
  147. "The weights aim to approximate the accuracy of the paper. The accuracies are estimated on clip-level "
  148. "with parameters `frame_rate=15`, `clips_per_video=1`, and `clip_len=128`."
  149. ),
  150. "num_params": 8320048,
  151. "_metrics": {
  152. "Kinetics-400": {
  153. "acc@1": 68.368,
  154. "acc@5": 88.050,
  155. }
  156. },
  157. "_ops": 17.979,
  158. "_file_size": 31.972,
  159. },
  160. )
  161. DEFAULT = KINETICS400_V1
  162. @register_model()
  163. @handle_legacy_interface(weights=("pretrained", S3D_Weights.KINETICS400_V1))
  164. def s3d(*, weights: Optional[S3D_Weights] = None, progress: bool = True, **kwargs: Any) -> S3D:
  165. """Construct Separable 3D CNN model.
  166. Reference: `Rethinking Spatiotemporal Feature Learning <https://arxiv.org/abs/1712.04851>`__.
  167. .. betastatus:: video module
  168. Args:
  169. weights (:class:`~torchvision.models.video.S3D_Weights`, optional): The
  170. pretrained weights to use. See
  171. :class:`~torchvision.models.video.S3D_Weights`
  172. below for more details, and possible values. By default, no
  173. pre-trained weights are used.
  174. progress (bool): If True, displays a progress bar of the download to stderr. Default is True.
  175. **kwargs: parameters passed to the ``torchvision.models.video.S3D`` base class.
  176. Please refer to the `source code
  177. <https://github.com/pytorch/vision/blob/main/torchvision/models/video/s3d.py>`_
  178. for more details about this class.
  179. .. autoclass:: torchvision.models.video.S3D_Weights
  180. :members:
  181. """
  182. weights = S3D_Weights.verify(weights)
  183. if weights is not None:
  184. _ovewrite_named_param(kwargs, "num_classes", len(weights.meta["categories"]))
  185. model = S3D(**kwargs)
  186. if weights is not None:
  187. model.load_state_dict(weights.get_state_dict(progress=progress, check_hash=True))
  188. return model