CoolFace
Apppublic

HuggingFaceM4/IDEFICS_Data_Measurement_Tool

sourceHugging Faceupdated 3y agoView on Hugging Face
2likes
gradio_utils.py488 linesDownload Raw Back to utils
1# Copyright 2021 The HuggingFace Team. All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15import logging16 17import gradio as gr18import numpy as np19import pandas as pd20from matplotlib.figure import Figure21import seaborn as sns22import statistics23import streamlit as st24import utils25import utils.dataset_utils as ds_utils26#from st_aggrid import AgGrid, GridOptionsBuilder --> commenting out to fix local build error27from utils.dataset_utils import HF_DESC_FIELD, HF_FEATURE_FIELD, HF_LABEL_FIELD28 29logs = utils.prepare_logging(__file__)30st.set_option('deprecation.showPyplotGlobalUse', False)31 32# Note: Make sure to consider colorblind-friendly colors for your images! Ex:33# ["#332288", "#117733", "#882255", "#AA4499", "#CC6677", "#44AA99", "#DDCC77",34# "#88CCEE"]35 36pd.options.display.float_format = "{:,.3f}".format # '{:20,.2f}'.format37 38def subheader():39    gr.Markdown("""This demo showcases the 40    [dataset metrics as we develop them](https://huggingface.co/blog/data-measurements-tool).41    Right now this has:42    - dynamic loading of datasets in the lib43    - fetching config and info without downloading the dataset44    - propose the list of candidate text and label features to select.45    """)46 47 48def get_label_names(dataset_name: str, config_name: str, ds_name_to_dict):49    label_field, label_names = (50        ds_name_to_dict[dataset_name][config_name][HF_FEATURE_FIELD][51            HF_LABEL_FIELD][0]52        if len(53            ds_name_to_dict[dataset_name][config_name][HF_FEATURE_FIELD][54                HF_LABEL_FIELD]55        ) > 056        else ((), [])57    )58    return label_field, label_names59 60 61 62def update_dataset(dataset_name: str, ds_name_to_dict):63    # choose a config to analyze64    ds_configs = ds_name_to_dict[dataset_name]65    # special handling for the largest-by-far dataset, C466    if dataset_name == "c4":67        config_names = ['en', 'en.noblocklist', 'realnewslike']68    else:69        config_names = list(ds_configs.keys())70 71    config_name = config_names[0]72    ds_config = ds_configs[config_name]73 74    text_features = ds_config[HF_FEATURE_FIELD]["string"]75    text_features = [('text',)] if dataset_name == "c4" else [tp for tp in text_features if tp[0] != "id"]76    feature = str(text_features[0])77    text_features = [str(f) for f in text_features]78 79    avail_splits = list(ds_config["splits"].keys())80    split = avail_splits[0]81 82    return [(config_names, config_name), (text_features, feature), (avail_splits, split)]83 84 85def update_config(dataset_name: str, config_name: str, ds_name_to_dict):86    ds_config = ds_name_to_dict[dataset_name][config_name]87 88    text_features = ds_config[HF_FEATURE_FIELD]["string"]89    text_features = [('text',)] if dataset_name == "c4" else [tp for tp in text_features if tp[0] != "id"]90    feature = str(text_features[0])91    text_features = [str(f) for f in text_features]92 93    avail_splits = list(ds_config["splits"].keys())94    split = avail_splits[0]95 96    return [(text_features, feature), (avail_splits, split)]97 98 99def sidebar_selection(ds_name_to_dict, column_id=""):100    ds_names = list(ds_name_to_dict.keys())101    with gr.Accordion(f"Choose dataset and field {column_id}", open=True):102        subheader()103        # choose a dataset to analyze104        ds_name = gr.Dropdown(105            label=f"Choose dataset to explore{column_id}:",106            choices=ds_names,107            value="hate_speech18",108        )109        # choose a config to analyze110        ds_configs = ds_name_to_dict[ds_name.value]111        # special handling for the largest-by-far dataset, C4112        if ds_name == "c4":113            config_names = ['en', 'en.noblocklist', 'realnewslike']114        else:115            config_names = list(ds_configs.keys())116        config_name = gr.Dropdown(117            label=f"Choose configuration{column_id}:",118            choices=config_names,119            value=config_names[0],120        )121        # choose a subset of num_examples122        ds_config = ds_configs[config_name.value]123        text_features = ds_config[HF_FEATURE_FIELD]["string"]124        # TODO @yacine: Explain what this is doing and why eg tp[0] could = "id"125        text = f"Which text feature from the {column_id} dataset would you like to analyze?"126        choices = [('text',)] if ds_name == "c4" else [tp for tp in text_features if tp[0] != "id"]127        text_field = gr.Dropdown(128            label=text,129            choices=[str(f) for f in choices],130            value=str(choices[0])131        )132        # Choose a split and dataset size133        avail_splits = list(ds_config["splits"].keys())134        # 12.Nov note: Removing "test" because those should not be examined135        # without discussion of pros and cons, which we haven't done yet.136        if "test" in avail_splits:137            avail_splits.remove("test")138        split = gr.Dropdown(139            label=f"Which split from the{column_id} dataset would you like to analyze?",140            choices=avail_splits,141            value=avail_splits[0],142        )143        label_field, label_names = get_label_names(ds_name.value, config_name.value, ds_name_to_dict)144        calculate_btn = gr.Button(value="Calculate", variant="primary")145        return {146            "dset_name": ds_name,147            "dset_config": config_name,148            "split_name": split,149            "text_field": text_field,150            "label_field": label_field,151            "label_names": label_names,152            "calculate_btn": calculate_btn153        }154 155 156def expander_header(dstats, ds_name_to_dict, column_id=""):157    with st.expander(f"Dataset Description{column_id}"):158        st.markdown(159            ds_name_to_dict[dstats.dset_name][dstats.dset_config][HF_DESC_FIELD]160        )161        st.dataframe(dstats.dset_peek)162 163 164def expander_general_stats(dstats, column_id=""):165    with gr.Accordion(f"General Text Statistics{column_id}"):166        st.caption(167            "Use this widget to check whether the terms you see most "168            "represented in the dataset make sense for the goals of the dataset."169        )170        st.markdown("There are {0} total words".format(str(dstats.total_words)))171        st.markdown(172            "There are {0} words after removing closed "173            "class words".format(str(dstats.total_open_words))174        )175        st.markdown(176            "The most common "177            "[open class words](https://dictionary.apa.org/open-class-words) "178            "and their counts are: "179        )180        st.dataframe(dstats.sorted_top_vocab_df)181        st.markdown(182            "There are {0} missing values in the dataset.".format(183                str(dstats.text_nan_count)184            )185        )186        if dstats.dups_frac > 0:187            st.markdown(188                "The dataset is {0}% duplicates. "189                "For more information about the duplicates, "190                "click the 'Duplicates' tab below.".format(191                    str(round(dstats.dups_frac * 100, 2)))192            )193        else:194            st.markdown("There are 0 duplicate items in the dataset. ")195 196 197def expander_label_distribution(dstats, column_id=""):198    with st.expander(f"Label Distribution{column_id}", expanded=False):199        st.caption(200            "Use this widget to see how balanced the labels in your dataset are."201        )202        if dstats.fig_labels:203            st.plotly_chart(dstats.fig_labels, use_container_width=True)204        else:205            st.markdown("No labels were found in the dataset")206 207 208def expander_text_lengths(dstats, column_id=""):209    _TEXT_LENGTH_CAPTION = (210        "Use this widget to identify outliers, particularly suspiciously long "211        "outliers."212    )213    with st.expander(f"Text Lengths{column_id}", expanded=False):214        st.caption(_TEXT_LENGTH_CAPTION)215        st.markdown(216            "Below, you can see how the lengths of the text instances in your "217            "dataset are distributed."218        )219        st.markdown(220            "Any unexpected peaks or valleys in the distribution may help to "221            "identify instances you want to remove or augment."222        )223        st.markdown(224            "### Here is the count of different text lengths in "225            "your dataset:"226        )227        # When matplotlib first creates this, it's a Figure.228        # Once it's saved, then read back in,229        # it's an ndarray that must be displayed using st.image230        # (I know, lame).231        if isinstance(dstats.length_obj.fig_lengths, Figure):232            st.pyplot(dstats.length_obj.fig_lengths, use_container_width=True)233        else:234            try:235                st.image(dstats.length_obj.fig_lengths)236            except Exception as e:237                logs.exception("Hit exception for lengths figure:")238                logs.exception(e)239        st.markdown(240            "The average length of text instances is **"241            + str(round(dstats.length_obj.avg_length, 2))242            + " words**, with a standard deviation of **"243            + str(round(dstats.length_obj.std_length, 2))244            + "**."245        )246        if dstats.length_obj.lengths_df is not None:247            start_id_show_lengths = st.selectbox(248                "Show examples of length:",249                np.sort(dstats.length_obj.lengths_df["length"].unique())[::-1].tolist(),250                key=f"select_show_length_{column_id}",251            )252            st.table(253                dstats.length_obj.lengths_df[254                    dstats.length_obj.lengths_df["length"] == start_id_show_lengths255                ].set_index("length")256            )257 258 259def expander_text_duplicates(dstats, column_id=""):260    with st.expander(f"Text Duplicates{column_id}", expanded=False):261        st.caption(262            "Use this widget to identify text strings that appear more than "263            "once."264        )265        st.markdown(266            "A model's training and testing may be negatively affected by "267            "unwarranted duplicates "268            "([Lee et al., 2021](https://arxiv.org/abs/2107.06499))."269        )270        st.markdown("------")271        st.write(272            "### Here is the list of all the duplicated items and their counts "273            "in the dataset."274        )275        if not dstats.duplicates_results:276            st.write("There are no duplicates in this dataset! 🥳")277        else:278            st.write("The fraction of the data that is a duplicate is:")279            st.write(str(round(dstats.dups_frac, 4)))280            # TODO: Check if this is slow when the size is large --281            # Should we store as dataframes?282            # Dataframes allow this to be interactive.283            st.dataframe(ds_utils.counter_dict_to_df(dstats.dups_dict))284 285 286def expander_text_perplexities(dstats, column_id=""):287    with st.expander(f"Text Perplexities{column_id}", expanded=False):288        st.caption(289            "Use this widget to identify text perplexities from GPT-2."290        )291        st.markdown(292            """293            Outlier perplexities, especially very high values, could highlight 294            an issue with an example. Smaller variations should be interpreted 295            with more care, as they indicate how similar to the GPT-2 training 296            corpus the examples are rather than being reflective of general 297            linguistic properties.298            For more information on GPT-2, 299            see its [model card](https://hf.co/gpt2).300            """301        )302        st.markdown("------")303        st.write(304            "### Here is the list of the examples in the dataset, sorted by "305            "GPT-2 perplexity:"306        )307        if dstats.perplexities_df is None or dstats.perplexities_df.empty:308            st.write(309                "Perplexities have not been computed yet for this dataset, or "310                "this dataset is too large for the UI (> 1,000,000 examples).")311        else:312            st.dataframe(dstats.perplexities_df.reset_index(drop=True))313 314 315def expander_npmi_description(min_vocab):316    _NPMI_CAPTION = (317        "Use this widget to identify problematic biases and stereotypes in "318        "your data."319    )320    _NPMI_CAPTION1 = """321    nPMI scores for a word help to identify potentially322    problematic associations, ranked by how close the association is."""323    _NPMI_CAPTION2 = """324    nPMI bias scores for paired words help to identify how word325    associations are skewed between the selected selected words326    ([Aka et al., 2021](https://arxiv.org/abs/2103.03417)).327    """328 329    st.caption(_NPMI_CAPTION)330    st.markdown(_NPMI_CAPTION1)331    st.markdown(_NPMI_CAPTION2)332    st.markdown("  ")333    st.markdown(334        "You can select from gender and sexual orientation "335        "identity terms that appear in the dataset at least %s "336        "times." % min_vocab337    )338    st.markdown(339        "The resulting ranked words are those that co-occur with both "340        "identity terms.  "341    )342    st.markdown(343        "The more *positive* the score, the more associated the word is with "344        "the first identity term.  "345        "The more *negative* the score, the more associated the word is with "346        "the second identity term."347    )348 349 350def expander_zipf(dstats, column_id=""):351    z = dstats.z352    zipf_fig = dstats.zipf_fig353    with st.expander(354        f"Vocabulary Distribution{column_id}: Zipf's Law Fit", expanded=False355    ):356        try:357            _ZIPF_CAPTION = """This shows how close the observed language is to an ideal358            natural language distribution following [Zipf's law](https://en.wikipedia.org/wiki/Zipf%27s_law),359            calculated by minimizing the [Kolmogorov-Smirnov (KS) statistic](https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test)."""360 361            powerlaw_eq = r"""p(x) \propto x^{- \alpha}"""362            zipf_summary = (363                    "The optimal alpha based on this dataset is: **"364                    + str(round(z.alpha, 2))365                    + "**, with a KS distance of: **"366                    + str(round(z.ks_distance, 2))367            )368            zipf_summary += (369                    "**.  This was fit with a minimum rank value of: **"370                    + str(int(z.xmin))371                    + "**, which is the optimal rank *beyond which* the scaling regime of the power law fits best."372            )373 374            alpha_warning = "Your alpha value is a bit on the high side, which means that the distribution over words in this dataset is a bit unnatural. This could be due to non-language items throughout the dataset."375            xmin_warning = "The minimum rank for this fit is a bit on the high side, which means that the frequencies of your most common words aren't distributed as would be expected by Zipf's law."376            fit_results_table = pd.DataFrame.from_dict(377                {378                    r"Alpha:": [str("%.2f" % z.alpha)],379                    "KS distance:": [str("%.2f" % z.ks_distance)],380                    "Min rank:": [str("%s" % int(z.xmin))],381                },382                columns=["Results"],383                orient="index",384            )385            fit_results_table.index.name = column_id386            st.caption(387                "Use this widget for the counts of different words in your dataset, measuring the difference between the observed count and the expected count under Zipf's law."388            )389            st.markdown(_ZIPF_CAPTION)390            st.write(391                """392            A Zipfian distribution follows the power law: $p(x) \propto x^{-α}$393    with an ideal α value of 1."""394            )395            st.markdown(396                "In general, an alpha greater than 2 or a minimum rank greater than 10 (take with a grain of salt) means that your distribution is relativaly _unnatural_ for natural language. This can be a sign of mixed artefacts in the dataset, such as HTML markup."397            )398            st.markdown(399                "Below, you can see the counts of each word in your dataset vs. the expected number of counts following a Zipfian distribution."400            )401            st.markdown("-----")402            st.write("### Here is your dataset's Zipf results:")403            st.dataframe(fit_results_table)404            st.write(zipf_summary)405            # TODO: Nice UI version of the content in the comments.406            # st.markdown("\nThe KS test p-value is < %.2f" % z.ks_test.pvalue)407            # if z.ks_test.pvalue < 0.01:408            #    st.markdown(409            #        "\n Great news! Your data fits a powerlaw with a minimum KS " "distance of %.4f" % z.distance)410            # else:411            #    st.markdown("\n Sadly, your data does not fit a powerlaw. =(")412            # st.markdown("Checking the goodness of fit of our observed distribution")413            # st.markdown("to the hypothesized power law distribution")414            # st.markdown("using a Kolmogorov–Smirnov (KS) test.")415            st.plotly_chart(zipf_fig, use_container_width=True)416            if z.alpha > 2:417                st.markdown(alpha_warning)418            if z.xmin > 5:419                st.markdown(xmin_warning)420        except:421            st.write("Under construction!")422 423 424def npmi_widget(dstats, column_id=""):425    """426    Part of the UI, but providing for interaction.427    :param column_id:428    :param dstats:429    :return:430    """431    min_vocab = dstats.min_vocab_count432    npmi_stats = dstats.npmi_obj433    available_terms = npmi_stats.avail_identity_terms434    with st.expander(f"Word Association{column_id}: nPMI", expanded=False):435        if npmi_stats and len(available_terms) > 0:436            expander_npmi_description(min_vocab)437            st.markdown("-----")438            term1 = st.selectbox(439                f"What is the first term you want to select?{column_id}",440                available_terms,441            )442            term2 = st.selectbox(443                f"What is the second term you want to select?{column_id}",444                reversed(available_terms),445            )446            try:447                joint_npmi_df = npmi_stats.get_display(term1, term2)448                npmi_show(joint_npmi_df)449            except Exception as e:450                logs.exception(e)451                st.markdown(452                    "**WARNING!** The nPMI for these terms has not been"453                    " pre-computed, please re-run caching."454                )455        else:456            st.markdown("No words found co-occurring with both of the selected identity"457                " terms.")458 459 460def npmi_show(paired_results):461    if paired_results.empty:462        st.markdown(463            "No words that co-occur enough times for results! Or there's a 🐛."464            "  Or we're still computing this one. 🤷")465    else:466        logs.debug("Results to be shown in streamlit are")467        logs.debug(paired_results)468        s = pd.DataFrame(469            paired_results.sort_values(paired_results.columns[0], ascending=True))470        s.index.name = "word"471        bias_col = s.filter(like="bias").columns472        #count_cols = s.filter(like="count").columns473        # Keep the dataframe from being crazy big.474        if s.shape[0] > 10000:475            bias_thres = max(abs(s[s[0]][5000]),476                             abs(s[s[0]][-5000]))477            logs.info(f"filtering with bias threshold: {bias_thres}")478            s_filtered = s[s[0].abs() > bias_thres]479        else:480            s_filtered = s481        cm = sns.palplot(sns.diverging_palette(270, 36, s=99, l=48, n=16))482        out_df = s_filtered.style.background_gradient(subset=bias_col, cmap=cm).format(formatter="{:,.3f}").set_properties(**{"align": "center", "width":"100em"}).set_caption("nPMI scores between the selected identity terms and the words they both co-occur with")483        #set_properties(subset=count_cols, **{"width": "10em", "text-align": "center"}).484        # .format(subset=count_cols, formatter=int).485        #.format(subset=bias_col, formatter="{:,.3f}")486        st.write("### Here is your dataset's bias results:")487        st.dataframe(out_df)488