From ed77fadd34caaa0bd38adbd9ee9bbdca60e14ab6 Mon Sep 17 00:00:00 2001 From: gokul Date: Tue, 13 Jun 2023 22:21:35 +0530 Subject: [PATCH] Add more features to the reflector demo code repo --- 42min-StartupsTechTalk-AGENDA-FULL.txt | 47 ++++ README.md | 10 +- TWC.png | Bin 0 -> 43609 bytes agenda-headers.txt | 8 + config.ini | 7 +- file_util.py | 51 ++++ requirements.txt | 45 +++- transcript_timestamps.txt | Bin 0 -> 119611 bytes whisjax.py | 319 +++++++++++++++++++------ 9 files changed, 402 insertions(+), 85 deletions(-) create mode 100644 42min-StartupsTechTalk-AGENDA-FULL.txt create mode 100644 TWC.png create mode 100644 agenda-headers.txt create mode 100644 file_util.py create mode 100644 transcript_timestamps.txt diff --git a/42min-StartupsTechTalk-AGENDA-FULL.txt b/42min-StartupsTechTalk-AGENDA-FULL.txt new file mode 100644 index 00000000..8ad3ff1c --- /dev/null +++ b/42min-StartupsTechTalk-AGENDA-FULL.txt @@ -0,0 +1,47 @@ +AGENDA: Most important things to look for in a start up + +TAM: Make sure the market is sufficiently large than once they win they can get rewarded +- Medium sized markets that should be winner take all can work +- TAM needs to be realistic of direct market size + +Product market fit: Being in a good market with a product than can satisfy that market +- Solves a problem +- Builds a solution a customer wants to buy +- Either saves the customer something (time/money/pain) or gives them something (revenue/enjoyment) + +Unit economics: Profit for delivering all-in cost must be attractive (% or $ amount) +- Revenue minus direct costs +- Raw input costs (materials, variable labour), direct cost of delivering and servicing the sale +- Attractive as a % of sales so it can contribute to fixed overhead +- Look for high incremental contribution margin + +LTV CAC: Life-time value (revenue contribution) vs cost to acquire customer must be healthy +- LTV = Purchase value x number of purchases x customer lifespan +- CAC = All-in costs of sales + marketing over number of new customer additions +- Strong reputation leads to referrals leads to lower CAC. Want customers evangelizing product/service +- Rule of thumb higher than 3 + +Churn: Fits into LTV, low churn leads to higher LTV and helps keep future CAC down +- Selling to replenish revenue every year is hard +- Can run through entire customer base over time +- Low churn builds strong net dollar retention + +Business: Must have sufficient barriers to entry to ward off copy-cats once established +- High switching costs (lock-in) +- Addictive +- Steep learning curve once adopted (form of switching cost) +- Two sided liquidity +- Patents, IP, Branding +- No hyper-scaler who can roll over you quickly +- Scale could be a barrier to entry but works against most start-ups, not for them +- Once developed, answer question: Could a well funded competitor starting up today easily duplicate this business or is it cheaper to buy the start up? + +Founders: Must be religious about their product. Believe they will change the world against all odds. +- Just money in the bank is not enough to build a successful company. Just good tech not enough +to build a successful company +- Founders must be motivated to build something, not (all) about money. They would be doing +this for free because they believe in it. Not looking for quick score +- Founders must be persuasive. They will be asking others to sacrifice to make their dream come +to life. They will need to convince investors this company can work and deserves funding. +- Must understand who the customer is and what problem they are helping to solve. +- Founders aren’t expected to know all the preceding points in this document but have an understanding of most of this, and be able to offer a vision. \ No newline at end of file diff --git a/README.md b/README.md index 49607dbf..f016e26f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ To setup, 2) Run ``` export KMP_DUPLICATE_LIB_OK=True``` in Terminal. [This is taken care of in code, but not reflecting, Will fix this issue later.] 3) Run the script setup_depedencies.sh. - ``` chmod +x setup_dependecies.sh ``` + ``` chmod +x setup_dependencies.sh ``` ``` sh setup_dependencies.sh ``` @@ -31,13 +31,7 @@ To setup, ``` python3 whisjax.py "https://www.youtube.com/watch?v=ihf0S97oxuQ" --transcript transcript.txt summary.txt ``` - - -To run the current whisper-jax real time trial, - -You need to run one additional step which is - -``` pip install -r requirements.txt``` +5) ``` pip install -r requirements.txt``` diff --git a/TWC.png b/TWC.png new file mode 100644 index 0000000000000000000000000000000000000000..3f6abc71d8e3f3775a58cbd40026c3751dcba4fc GIT binary patch literal 43609 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 S2s6SJ5PcmJ?W&tjkN*#{eR(ec literal 0 HcmV?d00001 diff --git a/agenda-headers.txt b/agenda-headers.txt new file mode 100644 index 00000000..fd8034a2 --- /dev/null +++ b/agenda-headers.txt @@ -0,0 +1,8 @@ +AGENDA: Most important things to look for in a start up +TAM: Make sure the market is sufficiently large than once they win they can get rewarded +Product market fit: Being in a good market with a product than can satisfy that market +Unit economics: Profit for delivering all-in cost must be attractive (% or $ amount) +LTV CAC: Life-time value (revenue contribution) vs cost to acquire customer must be healthy +Churn: Fits into LTV, low churn leads to higher LTV and helps keep future CAC down +Business: Must have sufficient barriers to entry to ward off copy-cats once established +Founders: Must be religious about their product. Believe they will change the world against all odds. \ No newline at end of file diff --git a/config.ini b/config.ini index ad40ac0b..027896ff 100644 --- a/config.ini +++ b/config.ini @@ -2,6 +2,9 @@ # Set exception rule for OpenMP error to allow duplicate lib initialization KMP_DUPLICATE_LIB_OK=TRUE # Export OpenAI API Key -OPENAI_APIKEY=API_KEY +OPENAI_APIKEY=***REMOVED*** # Export Whisper Model Size -WHISPER_MODEL_SIZE=tiny \ No newline at end of file +WHISPER_MODEL_SIZE=tiny +AWS_ACCESS_KEY= +AWS_SECRET_KEY= +BUCKET_NAME='reflector-bucket' \ No newline at end of file diff --git a/file_util.py b/file_util.py new file mode 100644 index 00000000..6a4a4e40 --- /dev/null +++ b/file_util.py @@ -0,0 +1,51 @@ +import boto3 +import botocore +import configparser +from loguru import logger + +config = configparser.ConfigParser() +config.read('config.ini') + +BUCKET_NAME = 'reflector-bucket' + +s3 = boto3.client('s3', + aws_access_key_id=config["DEFAULT"]["AWS_ACCESS_KEY"], + aws_secret_access_key=config["DEFAULT"]["AWS_SECRET_KEY"]) + +def upload_files(files_to_upload): + """ + Upload a list of files to the configured S3 bucket + :param files_to_upload: + :return: + """ + for KEY in files_to_upload: + logger.info("Uploading file " + KEY) + try: + s3.upload_file(KEY, BUCKET_NAME, KEY) + except botocore.exceptions.ClientError as e: + print(e.response) + + +def download_files(files_to_download): + """ + Download a list of files from the configured S3 bucket + :param files_to_download: + :return: + """ + for KEY in files_to_download: + logger.info("Downloading file " + KEY) + try: + s3.download_file(BUCKET_NAME, KEY, KEY) + except botocore.exceptions.ClientError as e: + if e.response['Error']['Code'] == "404": + print("The object does not exist.") + else: + raise + + +if __name__ == "__main__": + import sys + if sys.argv[1] == "download": + download_files([sys.argv[2]]) + elif sys.argv[1] == "upload": + upload_files([sys.argv[2]]) diff --git a/requirements.txt b/requirements.txt index 13799945..7e2fc07d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,47 @@ pyaudio==0.2.13 keyboard==0.13.5 pynput==1.7.6 -wave==0.0.2 \ No newline at end of file +wave==0.0.2 +aiohttp==3.8.4 +aiosignal==1.3.1 +async-timeout==4.0.2 +attrs==23.1.0 +certifi==2023.5.7 +charset-normalizer==3.1.0 +decorator==4.4.2 +filelock==3.12.0 +frozenlist==1.3.3 +idna==3.4 +imageio==2.29.0 +imageio-ffmpeg==0.4.8 +Jinja2==3.1.2 +llvmlite==0.40.0 +loguru==0.7.0 +MarkupSafe==2.1.2 +more-itertools==9.1.0 +moviepy==1.0.3 +mpmath==1.3.0 +multidict==6.0.4 +networkx==3.1 +numba==0.57.0 +numpy==1.24.3 +openai==0.27.7 +openai-whisper @ git+https://github.com/openai/whisper.git@248b6cb124225dd263bb9bd32d060b6517e067f8 +Pillow==9.5.0 +proglog==0.1.10 +pytube==15.0.0 +regex==2023.5.5 +six==1.16.0 +sympy==1.12 +tiktoken==0.3.3 +torch==2.0.1 +tqdm==4.65.0 +typing_extensions==4.6.2 +urllib3 +yarl==1.9.2 +boto3==1.26.151 +nltk==3.8.1 +wordcloud +spacy +scattertext +pandas \ No newline at end of file diff --git a/transcript_timestamps.txt b/transcript_timestamps.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f8ff32c5494b5c1c3e139a20d1fc3136435b20f GIT binary patch literal 119611 zcmeIuF#!Mo0K%a4Pi+kkh(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM Y7%*VKfB^#r3>YwAz<>b*1`Hez4AV*g0RR91 literal 0 HcmV?d00001 diff --git a/whisjax.py b/whisjax.py index 31f2dc46..bbd51dad 100644 --- a/whisjax.py +++ b/whisjax.py @@ -4,31 +4,42 @@ # summarize https://www.sprocket.org/video/cheesemaking.mp4 summary.txt # summarize podcast.mp3 summary.txt -from urllib.parse import urlparse -from pytube import YouTube -from loguru import logger -from whisper_jax import FlaxWhisperPipline -import jax.numpy as jnp -import moviepy.editor import argparse -import tempfile -import whisper -import openai -import re +import ast import configparser +import jax.numpy as jnp +import matplotlib.pyplot as plt +import moviepy.editor +import moviepy.editor +import nltk import os +import pandas as pd +import re +import scattertext as st +import spacy +import tempfile +from loguru import logger +from pytube import YouTube +from transformers import BartTokenizer, BartForConditionalGeneration +from urllib.parse import urlparse +from whisper_jax import FlaxWhisperPipline +from wordcloud import WordCloud, STOPWORDS +from file_util import upload_files, download_files + +nltk.download('punkt') + +# Configurations can be found in config.ini. Set them properly before executing config = configparser.ConfigParser() config.read('config.ini') WHISPER_MODEL_SIZE = config['DEFAULT']["WHISPER_MODEL_SIZE"] -OPENAI_APIKEY = config['DEFAULT']["OPENAI_APIKEY"] - -MAX_WORDS_IN_CHUNK = 2500 -MAX_OUTPUT_TOKENS = 1000 - def init_argparse() -> argparse.ArgumentParser: + """ + Parse the CLI arguments + :return: parser object + """ parser = argparse.ArgumentParser( usage="%(prog)s [OPTIONS] ", description="Creates a transcript of a video or audio file, then summarizes it using ChatGPT." @@ -37,43 +48,185 @@ def init_argparse() -> argparse.ArgumentParser: parser.add_argument("-l", "--language", help="Language that the summary should be written in", type=str, default="english", choices=['english', 'spanish', 'french', 'german', 'romanian']) parser.add_argument("-t", "--transcript", help="Save a copy of the intermediary transcript file", type=str) + parser.add_argument( + "-m", "--model_name", help="Name or path of the BART model", + type=str, default="facebook/bart-base") parser.add_argument("location") parser.add_argument("output") return parser -def chunk_text(txt): - sentences = re.split('[.!?]', txt) - +def chunk_text(txt, max_chunk_length=500): + """ + Split text into smaller chunks. + :param txt: Text to be chunked + :param max_chunk_length: length of chunk + :return: chunked texts + """ + sentences = nltk.sent_tokenize(txt) chunks = [] - chunk = "" - size = 0 - - for s in sentences: - # Get the number of words in this sentence. - n = len(re.findall(r'\w+', s)) - - # Skip over empty sentences. - if n == 0: - continue - - # We need to break the text up into chunks so as not to exceed the max - # number of tokens accepted by the ChatGPT model. - if size + n > MAX_WORDS_IN_CHUNK: - chunks.append(chunk) - size = n - chunk = s + current_chunk = "" + for sentence in sentences: + if len(current_chunk) + len(sentence) < max_chunk_length: + current_chunk += f" {sentence.strip()}" else: - chunk = chunk + s - size = size + n - - if chunk: - chunks.append(chunk) - + chunks.append(current_chunk.strip()) + current_chunk = f"{sentence.strip()}" + chunks.append(current_chunk.strip()) return chunks +def summarize_chunks(chunks, tokenizer, model): + """ + Summarize each chunk using a summarizer model + :param chunks: + :param tokenizer: + :param model: + :return: + """ + summaries = [] + for c in chunks: + input_ids = tokenizer.encode(c, return_tensors='pt') + summary_ids = model.generate( + input_ids, num_beams=4, length_penalty=2.0, max_length=1024, no_repeat_ngram_size=3) + summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True) + summaries.append(summary) + return summaries + + +def create_wordcloud(): + """ + Create a basic word cloud visualization of transcribed text + :return: None. The wordcloud image is saved locally + """ + with open("transcript.txt", "r") as f: + transcription_text = f.read() + + stopwords = set(STOPWORDS) + + # python_mask = np.array(PIL.Image.open("download1.png")) + + wordcloud = WordCloud(height=800, width=800, + background_color='white', + stopwords=stopwords, + min_font_size=8).generate(transcription_text) + + # Plot wordcloud and save image + plt.figure(facecolor=None) + plt.imshow(wordcloud, interpolation="bilinear") + plt.axis("off") + plt.tight_layout(pad=0) + plt.savefig("wordcloud.png") + + +def create_talk_diff_scatter_viz(): + """ + Perform agenda vs transription diff to see covered topics. + Create a scatter plot of words in topics. + :return: None. Saved locally. + """ + spaCy_model = "en_core_web_md" + nlp = spacy.load(spaCy_model) + nlp.add_pipe('sentencizer') + + agenda_topics = [] + agenda = [] + # Load the agenda + with open("agenda-headers.txt", "r") as f: + for line in f.readlines(): + if line.strip(): + agenda.append(line.strip()) + agenda_topics.append(line.split(":")[0]) + + # Load the transcription with timestamp + with open("transcript_timestamps.txt", "r") as f: + transcription_timestamp_text = f.read() + + res = ast.literal_eval(transcription_timestamp_text) + chunks = res["chunks"] + + # create df for processing + df = pd.DataFrame.from_dict(res["chunks"]) + + covered_items = {} + # ts: timestamp + # Map each timestamped chunk with top1 and top2 matched agenda + ts_to_topic_mapping_top_1 = {} + ts_to_topic_mapping_top_2 = {} + + # Also create a mapping of the different timestamps in which each topic was covered + topic_to_ts_mapping_top_1 = {} + topic_to_ts_mapping_top_2 = {} + + similarity_threshold = 0.7 + + for c in chunks: + doc_transcription = nlp(c["text"]) + topic_similarities = [] + for item in range(len(agenda)): + item_doc = nlp(agenda[item]) + # if not doc_transcription or not all(token.has_vector for token in doc_transcription): + if not doc_transcription: + continue + similarity = doc_transcription.similarity(item_doc) + topic_similarities.append((item, similarity)) + topic_similarities.sort(key=lambda x: x[1], reverse=True) + for i in range(2): + if topic_similarities[i][1] >= similarity_threshold: + covered_items[agenda[topic_similarities[i][0]]] = True + # top1 match + if i == 0: + ts_to_topic_mapping_top_1[c["timestamp"]] = agenda_topics[topic_similarities[i][0]] + topic_to_ts_mapping_top_1[agenda_topics[topic_similarities[i][0]]] = c["timestamp"] + # top2 match + else: + ts_to_topic_mapping_top_2[c["timestamp"]] = agenda_topics[topic_similarities[i][0]] + topic_to_ts_mapping_top_2[agenda_topics[topic_similarities[i][0]]] = c["timestamp"] + + + def create_new_columns(record): + """ + Accumulate the mapping information into the df + :param record: + :return: + """ + record["ts_to_topic_mapping_top_1"] = ts_to_topic_mapping_top_1[record["timestamp"]] + record["ts_to_topic_mapping_top_2"] = ts_to_topic_mapping_top_2[record["timestamp"]] + return record + + df = df.apply(create_new_columns, axis=1) + + # Count the number of items covered and calculatre the percentage + num_covered_items = sum(covered_items.values()) + percentage_covered = num_covered_items / len(agenda) * 100 + + # Print the results + print("πŸ’¬ Agenda items covered in the transcription:") + for item in agenda: + if item in covered_items and covered_items[item]: + print("βœ… ", item) + else: + print("❌ ", item) + print("πŸ“Š Coverage: {:.2f}%".format(percentage_covered)) + + # Save df for further experimentation + df.to_pickle("df.pkl") + + # Scatter plot of topics + df = df.assign(parse=lambda df: df.text.apply(st.whitespace_nlp_with_sentences)) + corpus = st.CorpusFromParsedDocuments( + df, category_col='ts_to_topic_mapping_top_1', parsed_col='parse' + ).build().get_unigram_corpus().compact(st.AssociationCompactor(2000)) + html = st.produce_scattertext_explorer( + corpus, + category='TAM', category_name='TAM', not_category_name='Churn', + minimum_term_frequency=0, pmi_threshold_coefficient=0, + width_in_pixels=1000, + transform=st.Scalers.dense_rank + ) + open('./demo_compact.html', 'w').write(html) + def main(): parser = init_argparse() args = parser.parse_args() @@ -83,6 +236,8 @@ def main(): # audio or video file. url = urlparse(args.location) + # S3 : Pull artefacts to S3 bucket ? + media_file = "" if url.scheme == 'http' or url.scheme == 'https': # Check if we're being asked to retreive a YouTube URL, which is handled @@ -103,65 +258,81 @@ def main(): logger.info(" XXX - This method hasn't been implemented yet.") elif url.scheme == '': media_file = url.path + # If file is not present locally, take it from S3 bucket + if not os.path.exists(media_file): + download_files([media_file]) else: print("Unsupported URL scheme: " + url.scheme) quit() - # If the media file we just retrieved is a video, extract its audio stream. - # XXX - We should be checking if we've downloaded an audio file (eg .mp3), - # XXX - in which case we can skip this step. For now we'll assume that - # XXX - everything is an mp4 video. - audio_filename = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name - logger.info(f"Extracting audio to: {audio_filename}") - - video = moviepy.editor.VideoFileClip(media_file) - video.audio.write_audiofile(audio_filename, logger=None) + # Handle video + try: + video = moviepy.editor.VideoFileClip(media_file) + audio_filename = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name + video.audio.write_audiofile(audio_filename, logger=None) + logger.info(f"Extracting audio to: {audio_filename}") + # Handle audio only file + except: + audio = moviepy.editor.AudioFileClip(media_file) + audio_filename = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name + audio.write_audiofile(audio_filename, logger=None) logger.info("Finished extracting audio") # Convert the audio to text using the OpenAI Whisper model - pipeline = FlaxWhisperPipline("openai/whisper-" + WHISPER_MODEL_SIZE, dtype=jnp.float16, batch_size=16) + pipeline = FlaxWhisperPipline("openai/whisper-" + WHISPER_MODEL_SIZE, + dtype=jnp.float16, + batch_size=16) whisper_result = pipeline(audio_filename, return_timestamps=True) logger.info("Finished transcribing file") - # If we got the transcript parameter on the command line, save the transcript to the specified file. + # If we got the transcript parameter on the command line, + # save the transcript to the specified file. if args.transcript: logger.info(f"Saving transcript to: {args.transcript}") transcript_file = open(args.transcript, "w") + transcript_file_timestamps = open(args.transcript[0:len(args.transcript)-4] + "_timestamps.txt", "w") transcript_file.write(whisper_result["text"]) + transcript_file_timestamps.write(str(whisper_result)) transcript_file.close() + transcript_file_timestamps.close() - # Summarize the generated transcript using OpenAI - openai.api_key = OPENAI_APIKEY + logger.info("Creating word cloud") + create_wordcloud() - # Break the text up into smaller chunks for ChatGPT to summarize. - logger.info(f"Breaking transcript up into smaller chunks with MAX_WORDS_IN_CHUNK = {MAX_WORDS_IN_CHUNK}") + logger.info("Performing talk-diff and talk-diff visualization") + create_talk_diff_scatter_viz() + + # S3 : Push artefacts to S3 bucket + files_to_upload = ["transcript.txt", "transcript_timestamps.txt", + "demo_compact.html", "df.pkl", + "wordcloud.png"] + upload_files(files_to_upload) + + # Summarize the generated transcript using the BART model + logger.info(f"Loading BART model: {args.model_name}") + tokenizer = BartTokenizer.from_pretrained(args.model_name) + model = BartForConditionalGeneration.from_pretrained(args.model_name) + + logger.info("Breaking transcript into smaller chunks") chunks = chunk_text(whisper_result['text']) - logger.info(f"Transcript broken up into {len(chunks)} chunks") - language = args.language + logger.info( + f"Transcript broken into {len(chunks)} chunks of at most 500 words") # TODO fix variable - logger.info(f"Writing summary text in {language} to: {args.output}") + logger.info(f"Writing summary text in {args.language} to: {args.output}") with open(args.output, 'w') as f: f.write('Summary of: ' + args.location + "\n\n") - - for c in chunks: - response = openai.ChatCompletion.create( - frequency_penalty=0.0, - max_tokens=1000, - model="gpt-3.5-turbo", - presence_penalty=1.0, - temperature=0.2, - messages=[ - {"role": "system", - "content": f"You are an assistant helping to summarize transcipts of an audio or video conversation. The summary should be written in the {language} language."}, - {"role": "user", "content": c} - ], - ) - f.write(response['choices'][0]['message']['content'] + "\n\n") + summaries = summarize_chunks(chunks, tokenizer, model) + for summary in summaries: + f.write(summary.strip() + "\n\n") logger.info("Summarization completed") + # Summarization takes a lot of time, so do this separately at the end + files_to_upload = ["summary.txt"] + upload_files(files_to_upload) + if __name__ == "__main__": main()