



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 						lowercase    :
						'''simple docstring'''


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







						def __call__(  self    :				Optional[int]	):



											'''simple docstring'''


											return self.pa_type







						def       lowerCamelCase_				(  self    :				List[str]       ,	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(snake_case       ,	snake_case	):
																SCREAMING_SNAKE_CASE							:     Tuple						=				np.array(snake_case	)

											if isinstance(snake_case       ,	snake_case	):
																return {"path": value, "bytes": None}
											elif isinstance(snake_case       ,	snake_case	):
																return {"path": None, "bytes": value}
											elif isinstance(snake_case       ,	np.ndarray	):
																# convert the image array to PNG/TIFF bytes
																return encode_np_array(snake_case	)
											elif isinstance(snake_case       ,	PIL.Image.Image	):
																# convert the PIL image to bytes (default format is PNG/TIFF)
																return encode_pil_image(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       lowerCamelCase_				(  self    :				Dict       ,	snake_case    :				dict       ,	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:
																SCREAMING_SNAKE_CASE							:     Dict						=				{}

											SCREAMING_SNAKE_CASE					,  SCREAMING_SNAKE_CASE							:     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(snake_case	):
																										SCREAMING_SNAKE_CASE							:     Optional[int]						=				PIL.Image.open(snake_case	)
																					else:
																										SCREAMING_SNAKE_CASE							:     str						=				path.split('::'	)[-1]
																										try:
																															SCREAMING_SNAKE_CASE							:     List[str]						=				string_to_dict(snake_case       ,	config.HUB_DATASETS_URL	)['repo_id']
																															SCREAMING_SNAKE_CASE							:     Optional[Any]						=				token_per_repo_id.get(snake_case	)
																										except ValueError:
																															SCREAMING_SNAKE_CASE							:     List[Any]						=				None
																										with xopen(snake_case       ,	'rb'       ,	use_auth_token=snake_case	) as f:
																															SCREAMING_SNAKE_CASE							:     Optional[Any]						=				BytesIO(f.read()	)
																										SCREAMING_SNAKE_CASE							:     List[str]						=				PIL.Image.open(bytes_	)
											else:
																SCREAMING_SNAKE_CASE							:     Dict						=				PIL.Image.open(BytesIO(bytes_	)	)
											image.load()  # to avoid "Too many open files" errors
											return image







						def       lowerCamelCase_				(  self    :				int	):



											'''simple docstring'''


											from .features import Value

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







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



											'''simple docstring'''


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







						def       lowerCamelCase_				(  self    :				List[str]       ,	snake_case    :				pa.StructArray	):



											'''simple docstring'''


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

											SCREAMING_SNAKE_CASE							:     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()       ,	)
											SCREAMING_SNAKE_CASE							:     int						=				pa.array(
											    [os.path.basename(snake_case	) if path is not None else None for path in storage.field('path'	).to_pylist()]       ,	type=pa.string()       ,	)
											SCREAMING_SNAKE_CASE							:     int						=				pa.StructArray.from_arrays([bytes_array, path_array]       ,	['bytes', 'path']       ,	mask=bytes_array.is_null()	)
											return array_cast(snake_case       ,	self.pa_type	)


def        __a			(   )     ->     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()
										SCREAMING_SNAKE_CASE							:     Optional[Any]						=				list(set(PIL.Image.OPEN.keys()   ) & set(PIL.Image.SAVE.keys()   )   )
					return _IMAGE_COMPRESSION_FORMATS

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

def        __a			(   __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        __a			(   __lowerCAmelCase   )     ->     dict:
					if config.PIL_AVAILABLE:
										import PIL.Image
					else:
										raise ImportError('To support encoding images, please install \'Pillow\'.'   )

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

					SCREAMING_SNAKE_CASE							:     Union[str, Any]						=				None

					# Multi-channel array case (only np.dtype("|u1") is allowed)
					if array.shape[2:]:
										SCREAMING_SNAKE_CASE							:     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:
										SCREAMING_SNAKE_CASE							:     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:
															SCREAMING_SNAKE_CASE							:     List[Any]						=				dtype_byteorder + dtype_kind + str(__lowerCAmelCase   )
															SCREAMING_SNAKE_CASE							:     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}'''   )
					SCREAMING_SNAKE_CASE							:     List[str]						=				PIL.Image.fromarray(array.astype(__lowerCAmelCase   )   )
					return {"path": None, "bytes": image_to_bytes(__lowerCAmelCase   )}



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

					if objs:
										SCREAMING_SNAKE_CASE					,  SCREAMING_SNAKE_CASE							:     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   ):
															SCREAMING_SNAKE_CASE							:     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   ):
															SCREAMING_SNAKE_CASE							:     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	)}