Paula Leonova commited on
Commit
01d9aad
1 Parent(s): b390e60

Upload latest files

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
README.md CHANGED
@@ -1,37 +1,17 @@
1
  ---
2
- title: Multi Label Summary Text
3
- emoji: 🐠
4
- colorFrom: gray
5
  colorTo: gray
6
  sdk: streamlit
7
  app_file: app.py
8
  pinned: false
9
  ---
10
 
11
- # Configuration
12
 
13
- `title`: _string_
14
- Display title for the Space
15
 
16
- `emoji`: _string_
17
- Space emoji (emoji-only character allowed)
18
 
19
- `colorFrom`: _string_
20
- Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gray)
21
-
22
- `colorTo`: _string_
23
- Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gray)
24
-
25
- `sdk`: _string_
26
- Can be either `gradio` or `streamlit`
27
-
28
- `sdk_version` : _string_
29
- Only applicable for `streamlit` SDK.
30
- See [doc](https://hf.co/docs/hub/spaces) for more info on supported versions.
31
-
32
- `app_file`: _string_
33
- Path to your main application file (which contains either `gradio` or `streamlit` Python code).
34
- Path is relative to the root of the repository.
35
-
36
- `pinned`: _boolean_
37
- Whether the Space stays on top of your list.
1
  ---
2
+ title: Multi Label Long Text
3
+ emoji: 📚
4
+ colorFrom: indigo
5
  colorTo: gray
6
  sdk: streamlit
7
  app_file: app.py
8
  pinned: false
9
  ---
10
 
11
+ **Interactive version**: This app is hosted on https://huggingface.co/spaces/pleonova/multi-label-long-text
12
 
13
+ **Objectvie**: As the name may suggest, the goal of this app is to identify multiple relevant labels for long text.
 
14
 
15
+ **Model**: zero-shot learning - facebook/bart-large-mnli summarizer and classifier
 
16
 
17
+ **Approach**: Updating the head of the neural network, we can use the same pretrained bart model to first summarize our long text by first splitting out our long text into chunks of 1024 tokens and then generating a summary for each of the text chunks. Next, all the summaries are concanenated and the bart model is used classify the summarized text. Alternatively, one can also classify the whole text as is.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
__pycache__/hf_model.cpython-37.pyc ADDED
Binary file (1.85 kB). View file
__pycache__/model.cpython-37.pyc ADDED
Binary file (1.85 kB). View file
__pycache__/models.cpython-37.pyc ADDED
Binary file (1.85 kB). View file
__pycache__/utils.cpython-37.pyc ADDED
Binary file (2.4 kB). View file
app.py CHANGED
@@ -1,4 +1,100 @@
 
 
 
 
 
 
1
  import streamlit as st
2
 
3
- x = st.slider('Select a value')
4
- st.write(x, 'squared is', x * x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reference: https://huggingface.co/spaces/team-zero-shot-nli/zero-shot-nli/blob/main/app.py
2
+
3
+ from os import write
4
+ import pandas as pd
5
+ import base64
6
+ from typing import Sequence
7
  import streamlit as st
8
 
9
+ from models import create_nest_sentences, load_summary_model, summarizer_gen, load_model, classifier_zero
10
+ from utils import plot_result, plot_dual_bar_chart, examples_load, example_long_text_load
11
+ # from utils import plot_result, examples_load, example_long_text_load, to_excel
12
+ import json
13
+
14
+
15
+ summarizer = load_summary_model()
16
+ classifier = load_model()
17
+ ex_text, ex_license, ex_labels = examples_load()
18
+ ex_long_text = example_long_text_load()
19
+
20
+
21
+ if __name__ == '__main__':
22
+ st.header("Summzarization & Multi-label Classification for Long Text")
23
+ st.write("This app summarizes and then classifies your long text with multiple labels (_output includes partial summaries, \
24
+ bar chart and data table of labels with confidence scores as well as a data download link_).")
25
+
26
+ with st.form(key='my_form'):
27
+ example_text = ex_long_text #ex_text
28
+ display_text = "[Excerpt from Project Gutenberg: Frankenstein]\n" + example_text + "\n\n" + ex_license
29
+ text_input = st.text_area("Input any text you want to summaryize & classify here (keep in mind very long text will take a while to process):", display_text)
30
+
31
+ if text_input == display_text:
32
+ text_input = example_text
33
+
34
+ # minimum_tokens = 30
35
+ # maximum_tokens = 100
36
+ labels = st.text_input('Possible labels (comma-separated):',ex_labels, max_chars=1000)
37
+ labels = list(set([x.strip() for x in labels.strip().split(',') if len(x.strip()) > 0]))
38
+ submit_button = st.form_submit_button(label='Submit')
39
+
40
+ if submit_button:
41
+ if len(labels) == 0:
42
+ st.write('Enter some text and at least one possible topic to see predictions.')
43
+
44
+
45
+
46
+ # For each body of text, create text chunks of a certain token size required for the transformer
47
+ nested_sentences = create_nest_sentences(document = text_input, token_max_length = 1024)
48
+
49
+ summary = []
50
+ st.markdown("### Text Chunk & Summaries")
51
+ st.markdown("Breaks up the original text into sections with complete sentences totaling \
52
+ less than 1024 tokens, a requirement for the summarizer.")
53
+
54
+ # For each chunk of sentences (within the token max), generate a summary
55
+ for n in range(0, len(nested_sentences)):
56
+ text_chunk = " ".join(map(str, nested_sentences[n]))
57
+ st.markdown(f"###### Chunk {n+1}/{len(nested_sentences)}" )
58
+ st.markdown(text_chunk)
59
+
60
+ chunk_summary = summarizer_gen(summarizer, sequence=text_chunk, maximum_tokens = 300, minimum_tokens = 20)
61
+ summary.append(chunk_summary)
62
+ st.markdown("###### Partial Summary")
63
+ st.markdown(chunk_summary)
64
+ # Combine all the summaries into a list and compress into one document, again
65
+ final_summary = " \n".join(list(summary))
66
+
67
+ # final_summary = summarizer_gen(summarizer, sequence=text_input, maximum_tokens = 30, minimum_tokens = 100)
68
+ st.markdown("### Combined Summary")
69
+ st.markdown(final_summary)
70
+
71
+ topics, scores = classifier_zero(classifier, sequence=final_summary, labels=labels, multi_class=True)
72
+
73
+ # st.markdown("### Top Label Predictions: Combined Summary")
74
+ # plot_result(topics[::-1][:], scores[::-1][:])
75
+
76
+ # st.markdown("### Download Data")
77
+ data = pd.DataFrame({'label': topics, 'scores_from_summary': scores})
78
+ # st.dataframe(data)
79
+
80
+ # coded_data = base64.b64encode(data.to_csv(index = False). encode ()).decode()
81
+ # st.markdown(
82
+ # f'<a href="data:file/csv;base64, {coded_data}" download = "data.csv">Download Data</a>',
83
+ # unsafe_allow_html = True
84
+ # )
85
+
86
+
87
+ st.markdown("### Top Label Predictions: Summary & Full Text")
88
+ topics_ex_text, scores_ex_text = classifier_zero(classifier, sequence=example_text, labels=labels, multi_class=True)
89
+ plot_dual_bar_chart(topics, scores, topics_ex_text, scores_ex_text)
90
+
91
+ data_ex_text = pd.DataFrame({'label': topics_ex_text, 'scores_from_full_text': scores_ex_text})
92
+ data2 = pd.merge(data, data_ex_text, on = ['label'])
93
+ st.markdown("### Data Table")
94
+
95
+ coded_data = base64.b64encode(data2.to_csv(index = False). encode ()).decode()
96
+ st.markdown(
97
+ f'<a href="data:file/csv;base64, {coded_data}" download = "data.csv">Click here to download the data</a>',
98
+ unsafe_allow_html = True
99
+ )
100
+ st.dataframe(data2)
example_long_text.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ I returned home not disappointed, for I have said that I had long considered those authors useless whom the professor reprobated; but I returned not at all the more inclined to recur to these studies in any shape. M. Krempe was a little squat man with a gruff voice and a repulsive countenance; the teacher, therefore, did not prepossess me in favour of his pursuits. In rather a too philosophical and connected a strain, perhaps, I have given an account of the conclusions I had come to concerning them in my early years. As a child I had not been content with the results promised by the modern professors of natural science. With a confusion of ideas only to be accounted for by my extreme youth and my want of a guide on such matters, I had retrod the steps of knowledge along the paths of time and exchanged the discoveries of recent inquirers for the dreams of forgotten alchemists. Besides, I had a contempt for the uses of modern natural philosophy. It was very different when the masters of the science sought immortality and power; such views, although futile, were grand; but now the scene was changed. The ambition of the inquirer seemed to limit itself to the annihilation of those visions on which my interest in science was chiefly founded. I was required to exchange chimeras of boundless grandeur for realities of little worth.
3
+
4
+ Such were my reflections during the first two or three days of my residence at Ingolstadt, which were chiefly spent in becoming acquainted with the localities and the principal residents in my new abode. But as the ensuing week commenced, I thought of the information which M. Krempe had given me concerning the lectures. And although I could not consent to go and hear that little conceited fellow deliver sentences out of a pulpit, I recollected what he had said of M. Waldman, whom I had never seen, as he had hitherto been out of town.
5
+
6
+ Partly from curiosity and partly from idleness, I went into the lecturing room, which M. Waldman entered shortly after. This professor was very unlike his colleague. He appeared about fifty years of age, but with an aspect expressive of the greatest benevolence; a few grey hairs covered his temples, but those at the back of his head were nearly black. His person was short but remarkably erect and his voice the sweetest I had ever heard. He began his lecture by a recapitulation of the history of chemistry and the various improvements made by different men of learning, pronouncing with fervour the names of the most distinguished discoverers. He then took a cursory view of the present state of the science and explained many of its elementary terms. After having made a few preparatory experiments, he concluded with a panegyric upon modern chemistry, the terms of which I shall never forget:
7
+
8
+ “The ancient teachers of this science,” said he, “promised impossibilities and performed nothing. The modern masters promise very little; they know that metals cannot be transmuted and that the elixir of life is a chimera but these philosophers, whose hands seem only made to dabble in dirt, and their eyes to pore over the microscope or crucible, have indeed performed miracles. They penetrate into the recesses of nature and show how she works in her hiding-places. They ascend into the heavens; they have discovered how the blood circulates, and the nature of the air we breathe. They have acquired new and almost unlimited powers; they can command the thunders of heaven, mimic the earthquake, and even mock the invisible world with its own shadows.”
9
+
10
+ Such were the professor’s words—rather let me say such the words of the fate—enounced to destroy me. As he went on I felt as if my soul were grappling with a palpable enemy; one by one the various keys were touched which formed the mechanism of my being; chord after chord was sounded, and soon my mind was filled with one thought, one conception, one purpose. So much has been done, exclaimed the soul of Frankenstein—more, far more, will I achieve; treading in the steps already marked, I will pioneer a new way, explore unknown powers, and unfold to the world the deepest mysteries of creation.
11
+
12
+ I closed not my eyes that night. My internal being was in a state of insurrection and turmoil; I felt that order would thence arise, but I had no power to produce it. By degrees, after the morning’s dawn, sleep came. I awoke, and my yesternight’s thoughts were as a dream. There only remained a resolution to return to my ancient studies and to devote myself to a science for which I believed myself to possess a natural talent. On the same day I paid M. Waldman a visit. His manners in private were even more mild and attractive than in public, for there was a certain dignity in his mien during his lecture which in his own house was replaced by the greatest affability and kindness. I gave him pretty nearly the same account of my former pursuits as I had given to his fellow professor. He heard with attention the little narration concerning my studies and smiled at the names of Cornelius Agrippa and Paracelsus, but without the contempt that M. Krempe had exhibited. He said that “These were men to whose indefatigable zeal modern philosophers were indebted for most of the foundations of their knowledge. They had left to us, as an easier task, to give new names and arrange in connected classifications the facts which they in a great degree had been the instruments of bringing to light. The labours of men of genius, however erroneously directed, scarcely ever fail in ultimately turning to the solid advantage of mankind.” I listened to his statement, which was delivered without any presumption or affectation, and then added that his lecture had removed my prejudices against modern chemists; I expressed myself in measured terms, with the modesty and deference due from a youth to his instructor, without letting escape (inexperience in life would have made me ashamed) any of the enthusiasm which stimulated my intended labours. I requested his advice concerning the books I ought to procure.
13
+
14
+ “I am happy,” said M. Waldman, “to have gained a disciple; and if your application equals your ability, I have no doubt of your success. Chemistry is that branch of natural philosophy in which the greatest improvements have been and may be made; it is on that account that I have made it my peculiar study; but at the same time, I have not neglected the other branches of science. A man would make but a very sorry chemist if he attended to that department of human knowledge alone. If your wish is to become really a man of science and not merely a petty experimentalist, I should advise you to apply to every branch of natural philosophy, including mathematics.”
15
+
16
+ He then took me into his laboratory and explained to me the uses of his various machines, instructing me as to what I ought to procure and promising me the use of his own when I should have advanced far enough in the science not to derange their mechanism. He also gave me the list of books which I had requested, and I took my leave.
17
+
18
+ Thus ended a day memorable to me; it decided my future destiny.
examples.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ {
2
+ "text": "Such were the professor’s words—rather let me say such the words of the fate—enounced to destroy me. As he went on I felt as if my soul were grappling with a palpable enemy; one by one the various keys were touched which formed the mechanism of my being; chord after chord was sounded, and soon my mind was filled with one thought, one conception, one purpose. So much has been done, exclaimed the soul of Frankenstein—more, far more, will I achieve; treading in the steps already marked, I will pioneer a new way, explore unknown powers, and unfold to the world the deepest mysteries of creation.",
3
+ "long_text_license": "[This eBook is for the use of anyone anywhere in the United States and most other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.org. If you are not located in the United States, you will have to check the laws of the country where you are located before using this eBook.]",
4
+ "labels":"Batman,Science,Sound,Light,Creation,Optics,Eyes,Engineering,Color,Communication,Death"
5
+ }
models.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
3
+
4
+ import spacy
5
+ nlp = spacy.load('en_core_web_sm')
6
+
7
+ # Reference: https://discuss.huggingface.co/t/summarization-on-long-documents/920/7
8
+ def create_nest_sentences(document:str, token_max_length = 1024):
9
+ nested = []
10
+ sent = []
11
+ length = 0
12
+ tokenizer = AutoTokenizer.from_pretrained('facebook/bart-large-mnli')
13
+ tokens = nlp(document)
14
+
15
+ for sentence in tokens.sents:
16
+ tokens_in_sentence = tokenizer(str(sentence), truncation=False, padding=False)[0] # hugging face transformer tokenizer
17
+ length += len(tokens_in_sentence)
18
+
19
+ if length < token_max_length:
20
+ sent.append(sentence)
21
+ else:
22
+ nested.append(sent)
23
+ sent = []
24
+ length = 0
25
+
26
+ if sent:
27
+ nested.append(sent)
28
+ return nested
29
+
30
+ # Reference: https://huggingface.co/facebook/bart-large-mnli
31
+ def load_summary_model():
32
+ model_name = "facebook/bart-large-mnli"
33
+ summarizer = pipeline(task='summarization', model=model_name)
34
+ return summarizer
35
+
36
+
37
+ # def load_summary_model():
38
+ # model_name = "facebook/bart-large-mnli"
39
+ # tokenizer = BartTokenizer.from_pretrained(model_name)
40
+ # model = BartForConditionalGeneration.from_pretrained(model_name)
41
+ # summarizer = pipeline(task='summarization', model=model, tokenizer=tokenizer, framework='pt')
42
+ # return summarizer
43
+
44
+
45
+ def summarizer_gen(summarizer, sequence:str, maximum_tokens:int, minimum_tokens:int):
46
+ output = summarizer(sequence, num_beams=4, max_length=maximum_tokens, min_length=minimum_tokens, do_sample=False)
47
+ return output[0].get('summary_text')
48
+
49
+
50
+ # # Reference: https://www.datatrigger.org/post/nlp_hugging_face/
51
+ # # Custom summarization pipeline (to handle long articles)
52
+ # def summarize(text, minimum_length_of_summary = 100):
53
+ # # Tokenize and truncate
54
+ # inputs = tokenizer_bart([text], truncation=True, max_length=1024, return_tensors='pt').to('cuda')
55
+ # # Generate summary
56
+ # summary_ids = model_bart.generate(inputs['input_ids'], num_beams=4, min_length = minimum_length_of_summary, max_length=400, early_stopping=True)
57
+ # # Untokenize
58
+ # return([tokenizer_bart.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=False) for g in summary_ids][0])
59
+
60
+
61
+ # Reference: https://huggingface.co/spaces/team-zero-shot-nli/zero-shot-nli/blob/main/utils.py
62
+ def load_model():
63
+ model_name = "facebook/bart-large-mnli"
64
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
65
+ model = AutoModelForSequenceClassification.from_pretrained(model_name)
66
+ classifier = pipeline(task='zero-shot-classification', model=model, tokenizer=tokenizer, framework='pt')
67
+ return classifier
68
+
69
+ def classifier_zero(classifier, sequence:str, labels:list, multi_class:bool):
70
+ outputs = classifier(sequence, labels, multi_label=multi_class)
71
+ return outputs['labels'], outputs['scores']
72
+
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ transformers[sentencepiece]==4.11.0
2
+ pandas
3
+ streamlit
4
+ plotly
5
+ torch
6
+ spacy>=2.2.0,<3.0.0
7
+ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.2.0/en_core_web_sm-2.2.0.tar.gz#egg=en_core_web_sm
utils.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ from plotly.subplots import make_subplots
6
+ import json
7
+
8
+ # Reference: https://huggingface.co/spaces/team-zero-shot-nli/zero-shot-nli/blob/main/utils.py
9
+ def plot_result(top_topics, scores):
10
+ top_topics = np.array(top_topics)
11
+ scores = np.array(scores)
12
+ scores *= 100
13
+ fig = px.bar(x=np.around(scores,2), y=top_topics, orientation='h',
14
+ labels={'x': 'Confidence Score', 'y': 'Label'},
15
+ text=scores,
16
+ range_x=(0,115),
17
+ title='Predictions',
18
+ color=np.linspace(0,1,len(scores)),
19
+ color_continuous_scale='GnBu')
20
+ fig.update(layout_coloraxis_showscale=False)
21
+ fig.update_traces(texttemplate='%{text:0.1f}%', textposition='outside')
22
+ st.plotly_chart(fig)
23
+
24
+
25
+ def plot_dual_bar_chart(topics_summary, scores_summary, topics_text, scores_text):
26
+ data1 = pd.DataFrame({'label': topics_summary, 'scores on summary': scores_summary})
27
+ data2 = pd.DataFrame({'label': topics_text, 'scores on full text': scores_text})
28
+ data = pd.merge(data1, data2, on = ['label'])
29
+ data.sort_values('scores on summary', ascending = True, inplace = True)
30
+
31
+ fig = make_subplots(rows=1, cols=2,
32
+ subplot_titles=("Predictions on Summary", "Predictions on Full Text"),
33
+ )
34
+
35
+ fig1 = px.bar(x=round(data['scores on summary']*100, 2), y=data['label'], orientation='h',
36
+ text=round(data['scores on summary']*100, 2),
37
+ )
38
+
39
+ fig2 = px.bar(x=round(data['scores on full text']*100,2), y=data['label'], orientation='h',
40
+ text=round(data['scores on full text']*100,2),
41
+ )
42
+
43
+ fig.add_trace(fig1['data'][0], row=1, col=1)
44
+ fig.add_trace(fig2['data'][0], row=1, col=2)
45
+
46
+ fig.update_traces(texttemplate='%{text:0.1f}%', textposition='outside')
47
+ fig.update_layout(height=600, width=700) #, title_text="Predictions for")
48
+ fig.update_xaxes(matches='x')
49
+ fig.update_yaxes(showticklabels=False) # hide all the xticks
50
+ fig.update_yaxes(showticklabels=True, row=1, col=1)
51
+
52
+
53
+ st.plotly_chart(fig)
54
+
55
+ # def plot_dual_bar_chart(topics_summary, scores_summary, topics_text, scores_text):
56
+ # data1 = pd.DataFrame({'label': topics_summary, 'scores': scores_summary})
57
+ # data1['classification_on'] = 'summary'
58
+ # data2 = pd.DataFrame({'label': topics_text, 'scores': scores_text})
59
+ # data2['classification_on'] = 'full text'
60
+ # data = pd.concat([data1, data2])
61
+ # data['scores'] = round(data['scores']*100,2)
62
+
63
+ # fig = px.bar(
64
+ # data, x="scores", y="label", #orientation = 'h',
65
+ # labels={'x': 'Confidence Score', 'y': 'Label'},
66
+ # text=data['scores'],
67
+ # range_x=(0,115),
68
+ # color="label", barmode="group",
69
+ # facet_col="classification_on",
70
+ # category_orders={"classification_on": ["summary", "full text"]}
71
+ # )
72
+ # fig.update_traces(texttemplate='%{text:0.1f}%', textposition='outside')
73
+
74
+ # st.plotly_chart(fig)
75
+
76
+
77
+ def examples_load():
78
+ with open("examples.json") as f:
79
+ data=json.load(f)
80
+ return data['text'], data['long_text_license'], data['labels']
81
+
82
+ def example_long_text_load():
83
+ with open("example_long_text.txt", "r") as f:
84
+ text_data = f.read()
85
+ return text_data