Yuvvan Talreja commited on
Commit
e9b9c89
·
1 Parent(s): 75f90c1

Updated transcription models

Browse files
Files changed (4) hide show
  1. Dockerfile +13 -9
  2. fastapi_app.py +91 -37
  3. templates/index.html +3 -3
  4. topic_segmenter.py +59 -9
Dockerfile CHANGED
@@ -10,6 +10,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
10
  && apt-get clean \
11
  && rm -rf /var/lib/apt/lists/*
12
 
 
 
 
 
13
  # Copy requirements first for better caching
14
  COPY requirements.txt .
15
  RUN pip install --no-cache-dir -r requirements.txt
@@ -17,17 +21,17 @@ RUN pip install --no-cache-dir -r requirements.txt
17
  # Install additional dependencies directly
18
  RUN pip install --no-cache-dir sentence-transformers>=2.2.2
19
 
20
- # Download model files - only use the sentence transformer
21
- RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"
22
-
23
- # Skip Whisper model preloading - it will be downloaded on first use
24
- # We don't need to preload it during build
25
 
26
- # Run NLTK downloads - add all needed NLTK data
27
- RUN python -c "import nltk; nltk.download('punkt'); nltk.download('wordnet'); nltk.download('omw-1.4')"
28
 
29
- # Cache the torch hub directory to avoid unnecessary downloads
30
- RUN mkdir -p /root/.cache/torch/hub
31
 
32
  # Copy the rest of the application
33
  COPY . .
 
10
  && apt-get clean \
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
+ # Create cache directories with proper permissions
14
+ RUN mkdir -p /tmp/transformers_cache /tmp/hf_home /tmp/xdg_cache /tmp/nltk_data \
15
+ && chmod 777 /tmp/transformers_cache /tmp/hf_home /tmp/xdg_cache /tmp/nltk_data
16
+
17
  # Copy requirements first for better caching
18
  COPY requirements.txt .
19
  RUN pip install --no-cache-dir -r requirements.txt
 
21
  # Install additional dependencies directly
22
  RUN pip install --no-cache-dir sentence-transformers>=2.2.2
23
 
24
+ # Setup environment variables
25
+ ENV TRANSFORMERS_CACHE="/tmp/transformers_cache"
26
+ ENV HF_HOME="/tmp/hf_home"
27
+ ENV XDG_CACHE_HOME="/tmp/xdg_cache"
28
+ ENV NLTK_DATA="/tmp/nltk_data"
29
 
30
+ # Download sentence transformer model to cache
31
+ RUN python -c "from sentence_transformers import SentenceTransformer; model = SentenceTransformer('all-MiniLM-L6-v2'); print(f'Model loaded successfully: {model}')"
32
 
33
+ # Run NLTK downloads to the tmp directory
34
+ RUN python -c "import nltk; nltk.data.path.append('/tmp/nltk_data'); nltk.download('punkt', download_dir='/tmp/nltk_data'); nltk.download('wordnet', download_dir='/tmp/nltk_data'); nltk.download('omw-1.4', download_dir='/tmp/nltk_data'); print('NLTK data downloaded successfully')"
35
 
36
  # Copy the rest of the application
37
  COPY . .
fastapi_app.py CHANGED
@@ -8,7 +8,7 @@ import base64
8
  import numpy as np
9
  import torch
10
  import time
11
- from transformers import pipeline
12
  import threading
13
  import json
14
  import logging
@@ -16,11 +16,32 @@ from pydantic import BaseModel
16
  from typing import Optional, Dict, Any, List
17
  from topic_segmenter import TopicSegmenter
18
  from middleware import HTTPSProxyMiddleware
 
19
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO)
22
  logger = logging.getLogger(__name__)
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  app = FastAPI(title="Flowify", description="Audio transcription and topic segmentation API")
25
 
26
  # Add custom middleware for HTTPS handling
@@ -74,50 +95,82 @@ def get_model(model_name):
74
  if model_name not in models:
75
  logging.info(f"Loading model: {model_name}")
76
 
77
- hf_model_name = model_name
78
  if model_name.startswith('Xenova/'):
79
- base_name = model_name.split('/')[-1]
80
- if '.en' in base_name:
81
- size = base_name.split('.')[0].replace('whisper-', '')
82
- hf_model_name = f"openai/whisper-{size}"
83
- else:
84
- size = base_name.replace('whisper-', '')
85
- hf_model_name = f"openai/whisper-{size}"
86
-
87
- logging.info(f"Converting Xenova model {model_name} to {hf_model_name}")
88
-
89
- try:
90
- # Simple direct approach without from_tf
91
- from transformers import pipeline
92
-
93
- models[model_name] = pipeline(
94
- "automatic-speech-recognition",
95
- hf_model_name,
96
- chunk_length_s=30,
97
- stride_length_s=5
98
- )
99
- logging.info(f"Model {model_name} loaded successfully using direct pipeline")
100
- except Exception as e:
101
- logging.error(f"Error loading model with direct pipeline: {str(e)}")
102
  try:
103
- # Try with explicit loading but no from_tf
104
- from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq, pipeline
 
 
 
 
 
 
 
 
 
105
 
106
- processor = AutoProcessor.from_pretrained(hf_model_name)
107
- model = AutoModelForSpeechSeq2Seq.from_pretrained(hf_model_name)
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  models[model_name] = pipeline(
110
- "automatic-speech-recognition",
111
  model=model,
112
- tokenizer=processor,
113
- feature_extractor=processor,
114
  chunk_length_s=30,
115
  stride_length_s=5
116
  )
117
- logging.info(f"Model {model_name} loaded successfully with explicit loading")
118
- except Exception as e2:
119
- logging.error(f"Failed to load model {model_name}: {str(e2)}")
120
- raise RuntimeError(f"Failed to load model {model_name}: {str(e2)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  return models[model_name]
122
 
123
  def process_audio(audio_data, sample_rate=16000, model_name="openai/whisper-base"):
@@ -192,7 +245,8 @@ async def analyze_topics(data: TranscriptData):
192
  min_segment_size=2,
193
  topic_similarity_threshold=0.25,
194
  max_topics=6,
195
- hierarchical_threshold=0.6
 
196
  )
197
 
198
  segments, topic_mappings, topic_history, topic_hierarchies = segmenter.segment_transcript(transcript)
 
8
  import numpy as np
9
  import torch
10
  import time
11
+ from transformers import pipeline, AutoProcessor, AutoModelForSpeechSeq2Seq
12
  import threading
13
  import json
14
  import logging
 
16
  from typing import Optional, Dict, Any, List
17
  from topic_segmenter import TopicSegmenter
18
  from middleware import HTTPSProxyMiddleware
19
+ import tempfile
20
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger(__name__)
24
 
25
+ # Set environment variables for cache directories to writable locations
26
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
27
+ os.environ["HF_HOME"] = "/tmp/hf_home"
28
+ os.environ["XDG_CACHE_HOME"] = "/tmp/xdg_cache"
29
+ os.environ["NLTK_DATA"] = "/tmp/nltk_data"
30
+
31
+ # Create directories
32
+ for directory in ["/tmp/transformers_cache", "/tmp/hf_home", "/tmp/xdg_cache", "/tmp/nltk_data"]:
33
+ os.makedirs(directory, exist_ok=True)
34
+
35
+ # Try to download NLTK data to the tmp directory
36
+ try:
37
+ import nltk
38
+ nltk.data.path.append("/tmp/nltk_data")
39
+ nltk.download('punkt', download_dir="/tmp/nltk_data", quiet=True)
40
+ nltk.download('wordnet', download_dir="/tmp/nltk_data", quiet=True)
41
+ nltk.download('omw-1.4', download_dir="/tmp/nltk_data", quiet=True)
42
+ except Exception as e:
43
+ logger.warning(f"NLTK download issue: {str(e)}")
44
+
45
  app = FastAPI(title="Flowify", description="Audio transcription and topic segmentation API")
46
 
47
  # Add custom middleware for HTTPS handling
 
95
  if model_name not in models:
96
  logging.info(f"Loading model: {model_name}")
97
 
98
+ # Handle Xenova models differently from OpenAI models
99
  if model_name.startswith('Xenova/'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  try:
101
+ # For Xenova models, we'll use a different approach
102
+ # We need to map to the corresponding HF model but handle it differently
103
+ base_name = model_name.split('/')[-1]
104
+
105
+ # Extract the size from the model name
106
+ if '.en' in base_name:
107
+ size = base_name.split('.')[0].replace('whisper-', '')
108
+ hf_model_name = f"openai/whisper-{size}"
109
+ else:
110
+ size = base_name.replace('whisper-', '')
111
+ hf_model_name = f"openai/whisper-{size}"
112
 
113
+ logging.info(f"Using Xenova format for {model_name}, loading from {hf_model_name}")
 
114
 
115
+ # Load processor and model specifically for Whisper
116
+ processor = AutoProcessor.from_pretrained(
117
+ hf_model_name,
118
+ cache_dir="/tmp/transformers_cache",
119
+ local_files_only=False
120
+ )
121
+
122
+ model = AutoModelForSpeechSeq2Seq.from_pretrained(
123
+ hf_model_name,
124
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
125
+ cache_dir="/tmp/transformers_cache",
126
+ local_files_only=False
127
+ )
128
+
129
+ # Create pipeline with the loaded model and processor
130
  models[model_name] = pipeline(
131
+ "automatic-speech-recognition",
132
  model=model,
133
+ tokenizer=processor.tokenizer,
134
+ feature_extractor=processor.feature_extractor,
135
  chunk_length_s=30,
136
  stride_length_s=5
137
  )
138
+ logging.info(f"Model {model_name} loaded successfully")
139
+
140
+ except Exception as e:
141
+ logging.error(f"Error loading Xenova model: {str(e)}")
142
+ raise RuntimeError(f"Failed to load Xenova model {model_name}: {str(e)}")
143
+ else:
144
+ # This is not a Xenova model, so we'll use the original approach
145
+ # but ensure we're using the cache directory
146
+ try:
147
+ processor = AutoProcessor.from_pretrained(
148
+ model_name,
149
+ cache_dir="/tmp/transformers_cache",
150
+ local_files_only=False
151
+ )
152
+
153
+ model = AutoModelForSpeechSeq2Seq.from_pretrained(
154
+ model_name,
155
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
156
+ cache_dir="/tmp/transformers_cache",
157
+ local_files_only=False
158
+ )
159
+
160
+ models[model_name] = pipeline(
161
+ "automatic-speech-recognition",
162
+ model=model,
163
+ tokenizer=processor.tokenizer,
164
+ feature_extractor=processor.feature_extractor,
165
+ chunk_length_s=30,
166
+ stride_length_s=5
167
+ )
168
+ logging.info(f"Model {model_name} loaded successfully")
169
+
170
+ except Exception as e:
171
+ logging.error(f"Failed to load model {model_name}: {str(e)}")
172
+ raise RuntimeError(f"Failed to load model {model_name}: {str(e)}")
173
+
174
  return models[model_name]
175
 
176
  def process_audio(audio_data, sample_rate=16000, model_name="openai/whisper-base"):
 
245
  min_segment_size=2,
246
  topic_similarity_threshold=0.25,
247
  max_topics=6,
248
+ hierarchical_threshold=0.6,
249
+ model_name="all-MiniLM-L6-v2"
250
  )
251
 
252
  segments, topic_mappings, topic_history, topic_hierarchies = segmenter.segment_transcript(transcript)
templates/index.html CHANGED
@@ -59,9 +59,9 @@
59
  <div class="select-wrapper">
60
  <select id="modelSelector">
61
  <option value="Xenova/whisper-tiny.en">Whisper Tiny (Fast)</option>
62
- <option value="Xenova/whisper-base.en">Whisper Base</option>
63
- <option value="Xenova/whisper-small.en" selected>Whisper Small</option>
64
- <option value="Xenova/whisper-medium.en">Whisper Medium</option>
65
  </select>
66
  <i class="fas fa-chevron-down select-icon"></i>
67
  </div>
 
59
  <div class="select-wrapper">
60
  <select id="modelSelector">
61
  <option value="Xenova/whisper-tiny.en">Whisper Tiny (Fast)</option>
62
+ <option value="Xenova/whisper-base.en" selected>Whisper Base</option>
63
+ <option value="Xenova/whisper-small.en">Whisper Small</option>
64
+ <option value="Xenova/whisper-medium.en">Whisper Medium (Slow)</option>
65
  </select>
66
  <i class="fas fa-chevron-down select-icon"></i>
67
  </div>
topic_segmenter.py CHANGED
@@ -1,22 +1,68 @@
1
  import numpy as np
2
  from sklearn.feature_extraction.text import TfidfVectorizer
3
  from sklearn.metrics.pairwise import cosine_similarity
4
- from nltk.tokenize import sent_tokenize, word_tokenize
5
- from nltk.stem import WordNetLemmatizer
6
- from keybert import KeyBERT
7
  import re
8
  from collections import defaultdict
9
  from sklearn.cluster import AgglomerativeClustering
10
  import logging
 
 
 
 
 
 
11
 
12
  # Ensure NLTK resources are available
13
  try:
14
  import nltk
15
- nltk.download('punkt', quiet=True)
16
- nltk.download('wordnet', quiet=True)
17
- nltk.download('omw-1.4', quiet=True)
 
 
 
 
 
 
 
 
 
18
  except Exception as e:
19
- logging.warning(f"NLTK resource download issue: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  class TopicSegmenter:
22
  def __init__(self, window_size=3, similarity_threshold=0.2, context_size=2, min_segment_size=2, topic_similarity_threshold=0.3,
@@ -34,16 +80,20 @@ class TopicSegmenter:
34
  import os
35
  os.environ["TOKENIZERS_PARALLELISM"] = "false" # Prevent parallelism warning
36
 
 
 
 
 
37
  # Try using sentence-transformers directly first
38
  try:
39
  from sentence_transformers import SentenceTransformer
40
- embedding_model = SentenceTransformer(model_name)
41
  self.keyword_extractor = KeyBERT(model=embedding_model)
42
  except (ImportError, ValueError, RuntimeError) as e:
43
  # Fallback to default model with lower-level settings
 
44
  self.keyword_extractor = KeyBERT(model='distilbert-base-nli-mean-tokens')
45
  except Exception as e:
46
- import logging
47
  logging.error(f"Error initializing KeyBERT model: {str(e)}")
48
  # Last resort fallback to simple keyword extraction without a model
49
  self.keyword_extractor = None
 
1
  import numpy as np
2
  from sklearn.feature_extraction.text import TfidfVectorizer
3
  from sklearn.metrics.pairwise import cosine_similarity
 
 
 
4
  import re
5
  from collections import defaultdict
6
  from sklearn.cluster import AgglomerativeClustering
7
  import logging
8
+ import os
9
+
10
+ # Set NLTK data path to tmp directory
11
+ nltk_data_dir = "/tmp/nltk_data"
12
+ os.makedirs(nltk_data_dir, exist_ok=True)
13
+ os.environ["NLTK_DATA"] = nltk_data_dir
14
 
15
  # Ensure NLTK resources are available
16
  try:
17
  import nltk
18
+ nltk.data.path.append(nltk_data_dir)
19
+
20
+ # Download resources if needed
21
+ for resource in ['punkt', 'wordnet', 'omw-1.4']:
22
+ try:
23
+ nltk.download(resource, download_dir=nltk_data_dir, quiet=True)
24
+ except Exception as e:
25
+ logging.warning(f"Error downloading NLTK resource {resource}: {str(e)}")
26
+
27
+ # Now import specific NLTK components
28
+ from nltk.tokenize import sent_tokenize, word_tokenize
29
+ from nltk.stem import WordNetLemmatizer
30
  except Exception as e:
31
+ logging.warning(f"NLTK import/setup issue: {str(e)}")
32
+ # Fallback implementations if NLTK fails
33
+ def sent_tokenize(text):
34
+ # Simple fallback sentence tokenizer
35
+ return text.replace('\n', ' ').split('. ')
36
+
37
+ def word_tokenize(text):
38
+ # Simple fallback word tokenizer
39
+ return text.split()
40
+
41
+ class WordNetLemmatizer:
42
+ def lemmatize(self, word):
43
+ return word
44
+
45
+ # Try to import KeyBERT, but provide fallback if it fails
46
+ try:
47
+ from keybert import KeyBERT
48
+ except ImportError as e:
49
+ logging.warning(f"KeyBERT import error: {str(e)}")
50
+ # Define a simple fallback KeyBERT class
51
+ class KeyBERT:
52
+ def __init__(self, model=None):
53
+ self.model = model
54
+
55
+ def extract_keywords(self, text, **kwargs):
56
+ # Simple fallback keyword extraction using frequency
57
+ if not text:
58
+ return [("topic", 1.0)]
59
+ words = text.lower().split()
60
+ word_count = defaultdict(int)
61
+ for word in words:
62
+ if len(word) > 3: # Skip very short words
63
+ word_count[word] += 1
64
+ sorted_words = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
65
+ return sorted_words[:kwargs.get('top_n', 5)]
66
 
67
  class TopicSegmenter:
68
  def __init__(self, window_size=3, similarity_threshold=0.2, context_size=2, min_segment_size=2, topic_similarity_threshold=0.3,
 
80
  import os
81
  os.environ["TOKENIZERS_PARALLELISM"] = "false" # Prevent parallelism warning
82
 
83
+ # Set cache directories
84
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
85
+ os.environ["HF_HOME"] = "/tmp/hf_home"
86
+
87
  # Try using sentence-transformers directly first
88
  try:
89
  from sentence_transformers import SentenceTransformer
90
+ embedding_model = SentenceTransformer(model_name, cache_folder="/tmp/transformers_cache")
91
  self.keyword_extractor = KeyBERT(model=embedding_model)
92
  except (ImportError, ValueError, RuntimeError) as e:
93
  # Fallback to default model with lower-level settings
94
+ logging.warning(f"Using fallback model for KeyBERT: {str(e)}")
95
  self.keyword_extractor = KeyBERT(model='distilbert-base-nli-mean-tokens')
96
  except Exception as e:
 
97
  logging.error(f"Error initializing KeyBERT model: {str(e)}")
98
  # Last resort fallback to simple keyword extraction without a model
99
  self.keyword_extractor = None