






import os
import sys
import warnings
from dataclasses import dataclass, field
from io import BytesIO
from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Union

import numpy as np
import pyarrow as pa

from .. import config
from ..download.streaming_download_manager import xopen
from ..table import array_cast
from ..utils.file_utils import is_local_path
from ..utils.py_utils import first_non_null_value, no_op_if_value_is_null, string_to_dict


if TYPE_CHECKING:
 import PIL.Image

 from .features import FeatureType


lowerCamelCase      :				Optional[List[str]]							=None
lowerCamelCase      :				Any							="""<""" if sys.byteorder == """little""" else """>"""
# Origin: https://github.com/python-pillow/Pillow/blob/698951e19e19972aeed56df686868f1329981c12/src/PIL/Image.py#L3126 minus "|i1" which values are not preserved correctly when saving and loading an image
lowerCamelCase      :				str							=[
    np.dtype('''|b1'''),
    np.dtype('''|u1'''),
    np.dtype('''<u2'''),
    np.dtype('''>u2'''),
    np.dtype('''<i2'''),
    np.dtype('''>i2'''),
    np.dtype('''<u4'''),
    np.dtype('''>u4'''),
    np.dtype('''<i4'''),
    np.dtype('''>i4'''),
    np.dtype('''<f4'''),
    np.dtype('''>f4'''),
    np.dtype('''<f8'''),
    np.dtype('''>f8'''),
]





@dataclass
class      __a    :
 _lowerCAmelCase			:			bool								=					True
 _lowerCAmelCase			:			Optional[str]								=					None
 # Automatically constructed
 _lowerCAmelCase			:			ClassVar[str]								=					"PIL.Image.Image"
 _lowerCAmelCase			:			ClassVar[Any]								=					pa.struct({'''bytes''': pa.binary(), '''path''': pa.string()}			)
 _lowerCAmelCase			:			str								=					field(default='''Image'''			,  init=SCREAMING_SNAKE_CASE_			,  repr=SCREAMING_SNAKE_CASE_			)







 def __call__(						self   :  Optional[int]		):


      '''simple docstring'''






      return self.pa_type







 def    __lowercase   (						self   :  List[str]				,  SCREAMING_SNAKE_CASE   :  Union[str, bytes, dict, np.ndarray, "PIL.Image.Image"]		):


      '''simple docstring'''






      if config.PIL_AVAILABLE:
           import PIL.Image
      else:
           raise ImportError("To support encoding images, please install \'Pillow\'."		)

      if isinstance(SCREAMING_SNAKE_CASE				,  SCREAMING_SNAKE_CASE		):
           UpperCamelCase__     :  Tuple										=    np.array(SCREAMING_SNAKE_CASE		)

      if isinstance(SCREAMING_SNAKE_CASE				,  SCREAMING_SNAKE_CASE		):
           return {"path": value, "bytes": None}
      elif isinstance(SCREAMING_SNAKE_CASE				,  SCREAMING_SNAKE_CASE		):
           return {"path": None, "bytes": value}
      elif isinstance(SCREAMING_SNAKE_CASE				,  np.ndarray		):
           # convert the image array to PNG/TIFF bytes
           return encode_np_array(SCREAMING_SNAKE_CASE		)
      elif isinstance(SCREAMING_SNAKE_CASE				,  PIL.Image.Image		):
           # convert the PIL image to bytes (default format is PNG/TIFF)
           return encode_pil_image(SCREAMING_SNAKE_CASE		)
      elif value.get("path"		) is not None and os.path.isfile(value["path"]		):
           # we set "bytes": None to not duplicate the data if they're already available locally
           return {"bytes": None, "path": value.get("path"		)}
      elif value.get("bytes"		) is not None or value.get("path"		) is not None:
           # store the image bytes, and path is used to infer the image format using the file extension
           return {"bytes": value.get("bytes"		), "path": value.get("path"		)}
      else:
           raise ValueError(
               F'An image sample should have one of \'path\' or \'bytes\' but they are missing or None in {value}.'		)







 def    __lowercase   (						self   :  Dict				,  SCREAMING_SNAKE_CASE   :  dict				,  SCREAMING_SNAKE_CASE   :  Dict=None		):


      '''simple docstring'''






      if not self.decode:
           raise RuntimeError("Decoding is disabled for this feature. Please use Image(decode=True) instead."		)

      if config.PIL_AVAILABLE:
           import PIL.Image
      else:
           raise ImportError("To support decoding images, please install \'Pillow\'."		)

      if token_per_repo_id is None:
           UpperCamelCase__     :  Dict										=    {}

      UpperCamelCase__     :  Optional[Any]										=    value['path'], value['bytes']
      if bytes_ is None:
           if path is None:
                raise ValueError(F'An image should have one of \'path\' or \'bytes\' but both are None in {value}.'		)
           else:
                if is_local_path(SCREAMING_SNAKE_CASE		):
                     UpperCamelCase__     :  Optional[int]										=    PIL.Image.open(SCREAMING_SNAKE_CASE		)
                else:
                     UpperCamelCase__     :  str										=    path.split("::"		)[-1]
                     try:
                          UpperCamelCase__     :  List[str]										=    string_to_dict(SCREAMING_SNAKE_CASE				,  config.HUB_DATASETS_URL		)['repo_id']
                          UpperCamelCase__     :  Optional[Any]										=    token_per_repo_id.get(SCREAMING_SNAKE_CASE		)
                     except ValueError:
                          UpperCamelCase__     :  List[Any]										=    None
                     with xopen(SCREAMING_SNAKE_CASE				,  "rb"				,  use_auth_token=SCREAMING_SNAKE_CASE		) as f:
                          UpperCamelCase__     :  Optional[Any]										=    BytesIO(f.read()		)
                     UpperCamelCase__     :  List[str]										=    PIL.Image.open(bytes_		)
      else:
           UpperCamelCase__     :  Dict										=    PIL.Image.open(BytesIO(bytes_		)		)
      image.load()  # to avoid "Too many open files" errors
      return image







 def    __lowercase   (						self   :  int		):


      '''simple docstring'''






      from .features import Value

      return (
          self
          if self.decode
          else {
              "bytes": Value("binary"		),
              "path": Value("string"		),
          }
      )







 def    __lowercase   (						self   :  List[str]				,  SCREAMING_SNAKE_CASE   :  Union[pa.StringArray, pa.StructArray, pa.ListArray]		):


      '''simple docstring'''






      if pa.types.is_string(storage.type		):
           UpperCamelCase__     :  Tuple										=    pa.array([None] * len(SCREAMING_SNAKE_CASE		)				,  type=pa.binary()		)
           UpperCamelCase__     :  str										=    pa.StructArray.from_arrays([bytes_array, storage]				,  ["bytes", "path"]				,  mask=storage.is_null()		)
      elif pa.types.is_binary(storage.type		):
           UpperCamelCase__     :  Optional[int]										=    pa.array([None] * len(SCREAMING_SNAKE_CASE		)				,  type=pa.string()		)
           UpperCamelCase__     :  int										=    pa.StructArray.from_arrays([storage, path_array]				,  ["bytes", "path"]				,  mask=storage.is_null()		)
      elif pa.types.is_struct(storage.type		):
           if storage.type.get_field_index("bytes"		) >= 0:
                UpperCamelCase__     :  Dict										=    storage.field("bytes"		)
           else:
                UpperCamelCase__     :  str										=    pa.array([None] * len(SCREAMING_SNAKE_CASE		)				,  type=pa.binary()		)
           if storage.type.get_field_index("path"		) >= 0:
                UpperCamelCase__     :  Any										=    storage.field("path"		)
           else:
                UpperCamelCase__     :  Dict										=    pa.array([None] * len(SCREAMING_SNAKE_CASE		)				,  type=pa.string()		)
           UpperCamelCase__     :  int										=    pa.StructArray.from_arrays([bytes_array, path_array]				,  ["bytes", "path"]				,  mask=storage.is_null()		)
      elif pa.types.is_list(storage.type		):
           UpperCamelCase__     :  Tuple										=    pa.array(
               [encode_np_array(np.array(SCREAMING_SNAKE_CASE		)		)["bytes"] if arr is not None else None for arr in storage.to_pylist()]				,  type=pa.binary()				,  )
           UpperCamelCase__     :  int										=    pa.array([None] * len(SCREAMING_SNAKE_CASE		)				,  type=pa.string()		)
           UpperCamelCase__     :  List[Any]										=    pa.StructArray.from_arrays(
               [bytes_array, path_array]				,  ["bytes", "path"]				,  mask=bytes_array.is_null()		)
      return array_cast(SCREAMING_SNAKE_CASE				,  self.pa_type		)







 def    __lowercase   (						self   :  List[str]				,  SCREAMING_SNAKE_CASE   :  pa.StructArray		):


      '''simple docstring'''






      @no_op_if_value_is_null
      def path_to_bytes(SCREAMING_SNAKE_CASE   :  Dict		):
           with xopen(SCREAMING_SNAKE_CASE				,  "rb"		) as f:
                UpperCamelCase__     :  List[Any]										=    f.read()
           return bytes_

      UpperCamelCase__     :  int										=    pa.array(
          [
              (path_to_bytes(x["path"]		) if x["bytes"] is None else x["bytes"]) if x is not None else None
              for x in storage.to_pylist()
          ]				,  type=pa.binary()				,  )
      UpperCamelCase__     :  int										=    pa.array(
          [os.path.basename(SCREAMING_SNAKE_CASE		) if path is not None else None for path in storage.field("path"		).to_pylist()]				,  type=pa.string()				,  )
      UpperCamelCase__     :  int										=    pa.StructArray.from_arrays([bytes_array, path_array]				,  ["bytes", "path"]				,  mask=bytes_array.is_null()		)
      return array_cast(SCREAMING_SNAKE_CASE				,  self.pa_type		)







def 						SCREAMING_SNAKE_CASE				(   )					->      List[str]:
     if config.PIL_AVAILABLE:
          import PIL.Image
     else:
          raise ImportError("To support encoding images, please install \'Pillow\'."     )

     global _IMAGE_COMPRESSION_FORMATS
     if _IMAGE_COMPRESSION_FORMATS is None:
          PIL.Image.init()
          UpperCamelCase__     :  Optional[Any]										=    list(set(PIL.Image.OPEN.keys()     ) & set(PIL.Image.SAVE.keys()     )     )
     return _IMAGE_COMPRESSION_FORMATS







def 						SCREAMING_SNAKE_CASE				(   __lowerCAmelCase     )					->      bytes:
     UpperCamelCase__     :  List[Any]										=    BytesIO()
     if image.format in list_image_compression_formats():
          UpperCamelCase__     :  Tuple										=    image.format
     else:
          UpperCamelCase__     :  Dict										=    'PNG' if image.mode in ['1', 'L', 'LA', 'RGB', 'RGBA'] else 'TIFF'
     image.save(__lowerCAmelCase   ,       format=__lowerCAmelCase     )
     return buffer.getvalue()







def 						SCREAMING_SNAKE_CASE				(   __lowerCAmelCase     )					->      dict:
     if hasattr(__lowerCAmelCase   ,       "filename"     ) and image.filename != "":
          return {"path": image.filename, "bytes": None}
     else:
          return {"path": None, "bytes": image_to_bytes(__lowerCAmelCase     )}







def 						SCREAMING_SNAKE_CASE				(   __lowerCAmelCase     )					->      dict:
     if config.PIL_AVAILABLE:
          import PIL.Image
     else:
          raise ImportError("To support encoding images, please install \'Pillow\'."     )

     UpperCamelCase__     :  Dict										=    array.dtype
     UpperCamelCase__     :  List[Any]										=    dtype.byteorder if dtype.byteorder != '=' else _NATIVE_BYTEORDER
     UpperCamelCase__     :  str										=    dtype.kind
     UpperCamelCase__     :  Dict										=    dtype.itemsize

     UpperCamelCase__     :  Union[str, Any]										=    None

     # Multi-channel array case (only np.dtype("|u1") is allowed)
     if array.shape[2:]:
          UpperCamelCase__     :  List[Any]										=    np.dtype("|u1"     )
          if dtype_kind not in ["u", "i"]:
               raise TypeError(
                   f'Unsupported array dtype {dtype} for image encoding. Only {dest_dtype} is supported for multi-channel arrays.'     )
          if dtype is not dest_dtype:
               warnings.warn(f'Downcasting array dtype {dtype} to {dest_dtype} to be compatible with \'Pillow\''     )
    # Exact match
     elif dtype in _VALID_IMAGE_ARRAY_DTPYES:
          UpperCamelCase__     :  Optional[Any]										=    dtype
     else:  # Downcast the type within the kind (np.can_cast(from_type, to_type, casting="same_kind") doesn't behave as expected, so do it manually)
          while dtype_itemsize >= 1:
               UpperCamelCase__     :  List[Any]										=    dtype_byteorder + dtype_kind + str(__lowerCAmelCase     )
               UpperCamelCase__     :  str										=    np.dtype(__lowerCAmelCase     )
               if dest_dtype in _VALID_IMAGE_ARRAY_DTPYES:
                    warnings.warn(f'Downcasting array dtype {dtype} to {dest_dtype} to be compatible with \'Pillow\''     )
                    break
               else:
                    dtype_itemsize //= 2

     if dest_dtype is None:
          raise TypeError(
              f'Cannot convert dtype {dtype} to a valid image dtype. Valid image dtypes: {_VALID_IMAGE_ARRAY_DTPYES}'     )
     UpperCamelCase__     :  List[str]										=    PIL.Image.fromarray(array.astype(__lowerCAmelCase     )     )
     return {"path": None, "bytes": image_to_bytes(__lowerCAmelCase     )}







def 						SCREAMING_SNAKE_CASE				(   __lowerCAmelCase     )					->      List[dict]:
     if config.PIL_AVAILABLE:
          import PIL.Image
     else:
          raise ImportError("To support encoding images, please install \'Pillow\'."     )

     if objs:
          UpperCamelCase__     :  Tuple										=    first_non_null_value(__lowerCAmelCase     )
          if isinstance(__lowerCAmelCase   ,       __lowerCAmelCase     ):
               return [{"path": obj, "bytes": None} if obj is not None else None for obj in objs]
          if isinstance(__lowerCAmelCase   ,       np.ndarray     ):
               UpperCamelCase__     :  List[Any]										=    no_op_if_value_is_null(__lowerCAmelCase     )
               return [obj_to_image_dict_func(__lowerCAmelCase     ) for obj in objs]
          elif isinstance(__lowerCAmelCase   ,       PIL.Image.Image     ):
               UpperCamelCase__     :  Optional[int]										=    no_op_if_value_is_null(__lowerCAmelCase     )
               return [obj_to_image_dict_func(__lowerCAmelCase     ) for obj in objs]
          else:
               return objs
     else:
          return objs




import numpy as np
import torch
from torch.nn import CrossEntropyLoss
from transformers import AutoModelForCausalLM, AutoTokenizer

import datasets
from datasets import logging


_lowerCamelCase    :		List[str]		      =    """\

"""

_lowerCamelCase    :		Optional[int]		      =    """
Perplexity (PPL) is one of the most common metrics for evaluating language models.
It is defined as the exponentiated average negative log-likelihood of a sequence.

For more information, see https://huggingface.co/docs/transformers/perplexity
"""

_lowerCamelCase    :		List[Any]		      =    """
Args:
    model_id (str): model used for calculating Perplexity
            NOTE: Perplexity can only be calculated for causal language models.
                    This includes models such as gpt2, causal variations of bert,
                    causal versions of t5, and more (the full list can be found
                    in the AutoModelForCausalLM documentation here:
                    https://huggingface.co/docs/transformers/master/en/model_doc/auto#transformers.AutoModelForCausalLM )

    input_texts (list of str): input text, each separate text snippet
        is one list entry.
    batch_size (int): the batch size to run texts through the model. Defaults to 16.
    add_start_token (bool): whether to add the start token to the texts,
        so the perplexity can include the probability of the first word. Defaults to True.
    device (str): device to run on, defaults to 'cuda' when available
Returns:
    perplexity: dictionary containing the perplexity scores for the texts
        in the input list, as well as the mean perplexity. If one of the input texts is
        longer than the max input length of the model, then it is truncated to the
        max length for the perplexity computation.
Examples:
    Example 1:
        >>> perplexity = datasets.load_metric(\"perplexity\")
        >>> input_texts = [\"lorem ipsum\", \"Happy Birthday!\", \"Bienvenue\"]
        >>> results = perplexity.compute(model_id='gpt2',
        ...                              add_start_token=False,
        ...                              input_texts=input_texts) # doctest:+ELLIPSIS
        >>> print(list(results.keys()))
        ['perplexities', 'mean_perplexity']
        >>> print(round(results[\"mean_perplexity\"], 2))
        78.22
        >>> print(round(results[\"perplexities\"][0], 2))
        11.11

    Example 2:
        >>> perplexity = datasets.load_metric(\"perplexity\")
        >>> input_texts = datasets.load_dataset(\"wikitext\",
        ...                                     \"wikitext-2-raw-v1\",
        ...                                     split=\"test\")[\"text\"][:50] # doctest:+ELLIPSIS
        [...]
        >>> input_texts = [s for s in input_texts if s!='']
        >>> results = perplexity.compute(model_id='gpt2',
        ...                              input_texts=input_texts) # doctest:+ELLIPSIS
        >>> print(list(results.keys()))
        ['perplexities', 'mean_perplexity']
        >>> print(round(results[\"mean_perplexity\"], 2))
        60.35
        >>> print(round(results[\"perplexities\"][0], 2))
        81.12
"""


@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION      ,    _KWARGS_DESCRIPTION)
class 						lowercase    (							datasets.Metric):
						'''simple docstring'''


						def       lowerCamelCase_				(  self    :				List[Any]	):



											'''simple docstring'''


											return datasets.MetricInfo(
											    description=_DESCRIPTION       ,	citation=_CITATION       ,	inputs_description=_KWARGS_DESCRIPTION       ,	features=datasets.Features(
											        {
											            'input_texts': datasets.Value('string'	),
											        }	)       ,	reference_urls=['https://huggingface.co/docs/transformers/perplexity']       ,	)







						def       lowerCamelCase_				(  self    :				List[Any]       ,	snake_case    :				Optional[int]       ,	snake_case    :				int       ,	snake_case    :				int = 16       ,	snake_case    :				bool = True       ,	snake_case    :				Dict=None	):



											'''simple docstring'''


											if device is not None:
																assert device in ["gpu", "cpu", "cuda"], "device should be either gpu or cpu."
																if device == "gpu":
																					SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				'cuda'
											else:
																SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				'cuda' if torch.cuda.is_available() else 'cpu'

											SCREAMING_SNAKE_CASE							:     Any						=				AutoModelForCausalLM.from_pretrained(snake_case	)
											SCREAMING_SNAKE_CASE							:     Dict						=				model.to(snake_case	)

											SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				AutoTokenizer.from_pretrained(snake_case	)

											# if batch_size > 1 (which generally leads to padding being required), and
											# if there is not an already assigned pad_token, assign an existing
											# special token to also be the padding token
											if tokenizer.pad_token is None and batch_size > 1:
																SCREAMING_SNAKE_CASE							:     str						=				list(tokenizer.special_tokens_map_extended.values()	)
																# check that the model already has at least one special token defined
																assert (
																    len(snake_case	) > 0
																), "If batch_size > 1, model must have at least one special token to use for padding. Please use a different model or set batch_size=1."
																# assign one of the special tokens to also be the pad token
																tokenizer.add_special_tokens({'pad_token': existing_special_tokens[0]}	)

											if add_start_token:
																# leave room for <BOS> token to be added:
																assert (
																    tokenizer.bos_token is not None
																), "Input model must already have a BOS token if using add_start_token=True. Please use a different model, or set add_start_token=False"
																SCREAMING_SNAKE_CASE							:     List[str]						=				model.config.max_length - 1
											else:
																SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				model.config.max_length

											SCREAMING_SNAKE_CASE							:     Tuple						=				tokenizer(
											    snake_case       ,	add_special_tokens=snake_case       ,	padding=snake_case       ,	truncation=snake_case       ,	max_length=snake_case       ,	return_tensors='pt'       ,	return_attention_mask=snake_case       ,	).to(snake_case	)

											SCREAMING_SNAKE_CASE							:     List[Any]						=				encodings['input_ids']
											SCREAMING_SNAKE_CASE							:     Dict						=				encodings['attention_mask']

											# check that each input is long enough:
											if add_start_token:
																assert torch.all(torch.ge(attn_masks.sum(1	)       ,	1	)	), "Each input text must be at least one token long."
											else:
																assert torch.all(
																    torch.ge(attn_masks.sum(1	)       ,	2	)	), "When add_start_token=False, each input text must be at least two tokens long. Run with add_start_token=True if inputting strings of only one token, and remove all empty input strings."

											SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				[]
											SCREAMING_SNAKE_CASE							:     Optional[Any]						=				CrossEntropyLoss(reduction='none'	)

											for start_index in logging.tqdm(range(0       ,	len(snake_case	)       ,	snake_case	)	):
																SCREAMING_SNAKE_CASE							:     Dict						=				min(start_index + batch_size       ,	len(snake_case	)	)
																SCREAMING_SNAKE_CASE							:     List[str]						=				encoded_texts[start_index:end_index]
																SCREAMING_SNAKE_CASE							:     Optional[int]						=				attn_masks[start_index:end_index]

																if add_start_token:
																					SCREAMING_SNAKE_CASE							:     List[Any]						=				torch.tensor([[tokenizer.bos_token_id]] * encoded_batch.size(dim=0	)	).to(snake_case	)
																					SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				torch.cat([bos_tokens_tensor, encoded_batch]       ,	dim=1	)
																					SCREAMING_SNAKE_CASE							:     int						=				torch.cat(
																					    [torch.ones(bos_tokens_tensor.size()       ,	dtype=torch.intaa	).to(snake_case	), attn_mask]       ,	dim=1	)

																SCREAMING_SNAKE_CASE							:     Optional[Any]						=				encoded_batch

																with torch.no_grad():
																					SCREAMING_SNAKE_CASE							:     str						=				model(snake_case       ,	attention_mask=snake_case	).logits

																SCREAMING_SNAKE_CASE							:     Optional[Any]						=				out_logits[..., :-1, :].contiguous()
																SCREAMING_SNAKE_CASE							:     Optional[int]						=				labels[..., 1:].contiguous()
																SCREAMING_SNAKE_CASE							:     Optional[int]						=				attn_mask[..., 1:].contiguous()

																SCREAMING_SNAKE_CASE							:     Optional[int]						=				torch.expa(
																    (loss_fct(shift_logits.transpose(1       ,	2	)       ,	snake_case	) * shift_attention_mask_batch).sum(1	)
																    / shift_attention_mask_batch.sum(1	)	)

																ppls += perplexity_batch.tolist()

											return {"perplexities": ppls, "mean_perplexity": np.mean(snake_case	)}