CoolFace
Apppublic

cffl/Exploring_Intelligent_Writing_Assistance

sourceHugging Faceapache-2.0updated 4y agoView on Hugging Face
9likes
style_classification.py246 linesDownload Raw Back to src
1# ###########################################################################2#3#  CLOUDERA APPLIED MACHINE LEARNING PROTOTYPE (AMP)4#  (C) Cloudera, Inc. 20225#  All rights reserved.6#7#  Applicable Open Source License: Apache 2.08#9#  NOTE: Cloudera open source products are modular software products10#  made up of hundreds of individual components, each of which was11#  individually copyrighted.  Each Cloudera open source product is a12#  collective work under U.S. Copyright Law. Your license to use the13#  collective work is as provided in your written agreement with14#  Cloudera.  Used apart from the collective work, this file is15#  licensed for your use pursuant to the open source license16#  identified above.17#18#  This code is provided to you pursuant a written agreement with19#  (i) Cloudera, Inc. or (ii) a third-party authorized to distribute20#  this code. If you do not have a written agreement with Cloudera nor21#  with an authorized and properly licensed third party, you do not22#  have any rights to access nor to use this code.23#24#  Absent a written agreement with Cloudera, Inc. (“Cloudera”) to the25#  contrary, A) CLOUDERA PROVIDES THIS CODE TO YOU WITHOUT WARRANTIES OF ANY26#  KIND; (B) CLOUDERA DISCLAIMS ANY AND ALL EXPRESS AND IMPLIED27#  WARRANTIES WITH RESPECT TO THIS CODE, INCLUDING BUT NOT LIMITED TO28#  IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND29#  FITNESS FOR A PARTICULAR PURPOSE; (C) CLOUDERA IS NOT LIABLE TO YOU,30#  AND WILL NOT DEFEND, INDEMNIFY, NOR HOLD YOU HARMLESS FOR ANY CLAIMS31#  ARISING FROM OR RELATED TO THE CODE; AND (D)WITH RESPECT TO YOUR EXERCISE32#  OF ANY RIGHTS GRANTED TO YOU FOR THE CODE, CLOUDERA IS NOT LIABLE FOR ANY33#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR34#  CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, DAMAGES35#  RELATED TO LOST REVENUE, LOST PROFITS, LOSS OF INCOME, LOSS OF36#  BUSINESS ADVANTAGE OR UNAVAILABILITY, OR LOSS OR CORRUPTION OF37#  DATA.38#39# ###########################################################################40 41from typing import List, Union42 43import torch44import numpy as np45from pyemd import emd46from transformers import pipeline47 48 49class StyleIntensityClassifier:50    """51    Utility for classifying style and calculating Style Transfer Intensity between52    two pieces of text (i.e. input and output of TST model).53 54    This custom evaluation metric aims to quantify the magnitude of transferred55    style between two texts. To accomplish this, we pass input and output texts56    through a trained style classifier to produce two distributions. We then57    utilize Earth Movers Distance (EMD) to calculate the minimum "cost"/"work"58    required to turn the input distribution into the output distribution. This59    metric allows us to capture a more nuanced, per-example measure of style60    transfer when compared to simply aggregating binary classifications over61    records in a dataset.62 63    Attributes:64        model_identifier (str)65 66    """67 68    def __init__(self, model_identifier: str):69        self.model_identifier = model_identifier70        self.device = torch.cuda.current_device() if torch.cuda.is_available() else -171        self._build_pipeline()72 73    def _build_pipeline(self):74 75        self.pipeline = pipeline(76            task="text-classification",77            model=self.model_identifier,78            device=self.device,79            return_all_scores=True,80        )81 82    def score(self, input_text: Union[str, List[str]]):83        """84        Classify a given input text using the model initialized by the class.85 86        Args:87            input_text (`str` or `List[str]`) - Input text for classification88 89        Returns:90            classification (dict) - a dictionary containing the label, score, and91                distribution between classes92 93        """94        if isinstance(input_text, str):95            tmp = list()96            tmp.append(input_text)97            input_text = tmp98 99        result = self.pipeline(input_text)100        distributions = np.array(101            [[label["score"] for label in item] for item in result]102        )103        return [104            {105                "label": self.pipeline.model.config.id2label[scores.argmax()],106                "score": round(scores.max(), 4),107                "distribution": scores.tolist(),108            }109            for scores in distributions110        ]111 112    def calculate_transfer_intensity(113        self, input_text: List[str], output_text: List[str], target_class_idx: int = 1114    ) -> List[float]:115        """116        Calcualates the style transfer intensity (STI) between two pieces of text.117 118        Args:119            input_text (list) - list of input texts with indicies corresponding120                to counterpart in output_text121            ouptput_text (list) - list of output texts with indicies corresponding122                to counterpart in input_text123            target_class_idx (int) - index of the target style class used for directional124                score correction125 126        Returns:127            A list of floats with corresponding style transfer intensity scores.128 129        """130 131        if len(input_text) != len(output_text):132            raise ValueError(133                "input_text and output_text must be of same length with corresponding items"134            )135 136        input_dist = [item["distribution"] for item in self.score(input_text)]137        output_dist = [item["distribution"] for item in self.score(output_text)]138 139        return [140            self.calculate_emd(input_dist[i], output_dist[i], target_class_idx)141            for i in range(len(input_dist))142        ]143 144    def calculate_transfer_intensity_fraction(145        self, input_text: List[str], output_text: List[str], target_class_idx: int = 1146    ) -> List[float]:147        """148        Calcualates the style transfer intensity (STI) _fraction_ between two pieces of text.149        See `calcualte_sti_fraction()` for details.150 151        Args:152            input_text (list) - list of input texts with indicies corresponding153                to counterpart in output_text154            ouptput_text (list) - list of output texts with indicies corresponding155                to counterpart in input_text156            target_class_idx (int) - index of the target style class used for directional157                score correction158 159        Returns:160            A list of floats with corresponding style transfer intensity scores.161 162        """163 164        if len(input_text) != len(output_text):165            raise ValueError(166                "input_text and output_text must be of same length with corresponding items"167            )168 169        input_dist = [item["distribution"] for item in self.score(input_text)]170        output_dist = [item["distribution"] for item in self.score(output_text)]171 172        return [173            self.calculate_sti_fraction(174                input_dist[i],175                output_dist[i],176                ideal_dist=[0.0, 1.0],177                target_class_idx=target_class_idx,178            )179            for i in range(len(input_dist))180        ]181 182    def calculate_sti_fraction(183        self, input_dist, output_dist, ideal_dist=[0.0, 1.0], target_class_idx=1184    ):185        """186        Calculate the direction-corrected style transfer intensity fraction between187        two style distributions of equal length.188 189        If output_dist moves closer towards target style class, the metric represents the percentage of190        the possible _target_ style distribution that was captured during the transfer. If output_dist191        moves further from the target style class, the metric represents the percentage of the possible192        _source_ style distribution that was captured.193 194        Args:195            input_dist (list) - probabilities assigned to the style classes196                from the input text to style transfer model197            output_dist (list) - probabilities assigned to the style classes198                from the outut text of the style transfer model199            ideal_dist (list, optional): The maximum possibly distribution. Defaults to [0.0, 1.0].200            target_class_idx (int, optional)201 202        Returns:203            sti_fraction (float)204        """205 206        sti = self.calculate_emd(input_dist, output_dist, target_class_idx)207 208        if sti > 0:209            potential = self.calculate_emd(input_dist, ideal_dist, target_class_idx)210        else:211            potential = self.calculate_emd(212                input_dist, ideal_dist[::-1], target_class_idx213            )214 215        return sti / potential216 217    @staticmethod218    def calculate_emd(input_dist, output_dist, target_class_idx):219        """220        Calculate the direction-corrected Earth Mover's Distance (aka Wasserstein distance)221        between two distributions of equal length. Here we penalize the EMD score if222        the output text style moved further away from the target style.223 224        Reference: https://github.com/passeul/style-transfer-model-evaluation/blob/master/code/style_transfer_intensity.py225 226        Args:227            input_dist (list) - probabilities assigned to the style classes228                from the input text to style transfer model229            output_dist (list) - probabilities assigned to the style classes230                from the outut text of the style transfer model231 232        Returns:233            emd (float) - Earth Movers Distance between the two distributions234 235        """236 237        N = len(input_dist)238        distance_matrix = np.ones((N, N))239        dist = emd(np.array(input_dist), np.array(output_dist), distance_matrix)240 241        transfer_direction_correction = (242            1 if output_dist[target_class_idx] >= input_dist[target_class_idx] else -1243        )244 245        return round(dist * transfer_direction_correction, 4)246