import streamlit as st import pandas as pd import numpy as np from io import StringIO from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import LatentDirichletAllocation, PCA from sklearn.cluster import KMeans from sklearn.manifold import TSNE import re from russpelling import * try: import maru except: pass from natasha import Doc, Segmenter, MorphVocab, NewsEmbedding, NewsMorphTagger import sys import time from threading import Thread from scipy.special import gammaln from scipy.stats import mode from scipy.sparse import csr_matrix, lil_matrix from umap import UMAP import plotly.express as px import scipy from hmmlearn.hmm import CategoricalHMM, GMMHMM from razdel import tokenize,sentenize from razdel.substring import Substring import colorsys from docx import Document from docx.shared import RGBColor, Pt from docx.enum.text import WD_COLOR_INDEX, WD_ALIGN_PARAGRAPH from soyclustering import SphericalKMeans from bertopic import BERTopic from sentence_transformers import SentenceTransformer import plotly.graph_objects as go from typing import List, Union from bertopic.backend._utils import select_backend import matplotlib.pyplot as plt from contextualized_topic_models.models.ctm import CombinedTM from contextualized_topic_models.utils.data_preparation import TopicModelDataPreparation, bert_embeddings_from_list import datetime import torch from contextualized_topic_models.datasets.dataset import CTMDataset import pyLDAvis as vis from streamlit import components from Top2VecNew import Top2VecNew from matplotlib.colors import rgb2hex from scipy.optimize import linear_sum_assignment import math from navec import Navec from scipy.ndimage import gaussian_filter1d from sklearn.metrics.pairwise import cosine_distances from sklearn.mixture import GaussianMixture st.set_page_config(layout='wide') token_pattern = '(?u)\\b\\w+(?:-\\w+)*\\b' token_pattern_filt = '(?u)\\b[^\\W0-9](?:[-\']?[^\\W0-9])+\\b' #token_pattern_filt = '[^ ][^ ]+' class ReturnableThread(Thread): def __init__(self, target, args=(), kwargs={}): super().__init__(target=target, args=args, kwargs=kwargs) self.result = None def run(self): if self._target: self.result = self._target(*self._args, **self._kwargs) def tm_data_prep_fit(self, text_for_contextual, text_for_bow, vectorizer=None): if text_for_bow is not None: assert len(text_for_contextual) == len(text_for_bow) if self.contextualized_model is None: raise Exception( "A contextualized model must be defined" ) # TODO: this count vectorizer removes tokens that have len = 1, might be unexpected for the users self.vectorizer = CountVectorizer() if not vectorizer else vectorizer train_bow_embeddings = self.vectorizer.fit_transform(text_for_bow) # if the user is passing custom embeddings we don't need to create the embeddings using the model train_contextualized_embeddings = bert_embeddings_from_list( text_for_contextual, sbert_model_to_load=self.contextualized_model, max_seq_length=self.max_seq_length, ) self.vocab = self.vectorizer.get_feature_names_out() self.id2token = {k: v for k, v in zip(range(0, len(self.vocab)), self.vocab)} encoded_labels = None return CTMDataset( X_contextual=train_contextualized_embeddings, X_bow=train_bow_embeddings, idx2token=self.id2token, labels=encoded_labels, ) def gmmfit(gmm, X, w=None, max_iter=1000, eps=1e-6): if w is None: w = np.ones((X.shape[0])) p0 = (gmm.score_samples(X) * w).sum() / w.sum() for i in range(max_iter): comps = w[:,None] * gmm.predict_proba(X) gmm.weights_ = comps.sum(axis=0) / comps.sum() comps /= comps.sum(axis=0) means = np.transpose(comps) @ X gmm.means_ = np.where(np.isnan(means), gmm.means_, means) covars = X[:,None,:] - gmm.means_ covars = (comps[...,None,None] * (covars[:,:,:,None] * covars[:,:,None,:])).sum(axis=0) gmm.covariances_ = np.where(np.isnan(covars), gmm.covariances_, covars) # КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: обновляем precisions_cholesky_ precisions_chol = np.empty_like(gmm.covariances_) for k, cov in enumerate(gmm.covariances_): stop = False eps = 1e-6 while not stop: val, vec = np.linalg.eig(cov) gmm.covariances_[k] = vec.real @ np.diag(np.maximum(val.real,eps)) @ vec.real.transpose() try: # Считаем нижнетреугольную матрицу разложения Холецкого cov_chol = np.linalg.cholesky(cov) stop = True except np.linalg.LinAlgError: eps *= 10 # Вычисляем матрицу точности (precision matrix) precisions_chol[k] = scipy.linalg.solve_triangular(cov_chol, np.eye(cov_chol.shape[0]), lower=True).T gmm.precisions_cholesky_ = precisions_chol p1 = (gmm.score_samples(X) * w).sum() / w.sum() if p1-p0 < eps: return p0 = p1 def visualize_documents( topic_model, docs: List[str], topics: List[int] = None, embeddings: np.ndarray = None, reduced_embeddings: np.ndarray = None, sample: float = None, hide_annotations: bool = False, hide_document_hover: bool = False, custom_labels: Union[bool, str] = False, title: str = "Документы и темы", width: int = 1200, height: int = 750, ): topic_per_doc = topic_model.topics_ if topics is None else topics # Sample the data to optimize for visualization and dimensionality reduction if sample is None or sample > 1: sample = 1 indices = [] for topic in set(topic_per_doc): s = np.where(np.array(topic_per_doc) == topic)[0] size = len(s) if len(s) < 100 else int(len(s) * sample) indices.extend(np.random.choice(s, size=size, replace=False)) indices = np.array(indices) df = pd.DataFrame({"topic": np.array(topic_per_doc)[indices]}) df["doc"] = [docs[index] for index in indices] df["topic"] = [topic_per_doc[index] for index in indices] # Extract embeddings if not already done if sample is None: if embeddings is None and reduced_embeddings is None: embeddings_to_reduce = topic_model._extract_embeddings(df.doc.to_list(), method="document") else: embeddings_to_reduce = embeddings else: if embeddings is not None: embeddings_to_reduce = embeddings[indices] elif embeddings is None and reduced_embeddings is None: embeddings_to_reduce = topic_model._extract_embeddings(df.doc.to_list(), method="document") # Reduce input embeddings if reduced_embeddings is None: try: from umap import UMAP umap_model = UMAP(n_neighbors=10, n_components=2, min_dist=0.0, metric="cosine").fit(embeddings_to_reduce) embeddings_2d = umap_model.embedding_ except (ImportError, ModuleNotFoundError): raise ModuleNotFoundError( "UMAP is required if the embeddings are not yet reduced in dimensionality. Please install it using `pip install umap-learn`." ) elif sample is not None and reduced_embeddings is not None: embeddings_2d = reduced_embeddings[indices] elif sample is None and reduced_embeddings is not None: embeddings_2d = reduced_embeddings unique_topics = set(topic_per_doc) if topics is None: topics = unique_topics # Combine data df["x"] = embeddings_2d[:, 0] df["y"] = embeddings_2d[:, 1] # Prepare text and names if isinstance(custom_labels, str): names = [[[str(topic), None]] + topic_model.topic_aspects_[custom_labels][topic] for topic in unique_topics] names = ["_".join([label[0] for label in labels[:4]]) for labels in names] names = [label if len(label) < 30 else label[:27] + "..." for label in names] elif topic_model.custom_labels_ is not None and custom_labels: names = [topic_model.custom_labels_[topic + topic_model._outliers] for topic in unique_topics] else: names = [ #f"{topic+1}_" + "_".join([word for word, value in topic_model.get_topic(topic)][:3]) f"{topic+1}_" + "_".join([word for word in st.session_state['topicnames'][topic].split(', ')[:3]]) for topic in unique_topics ] # Visualize fig = go.Figure() # Outliers and non-selected topics non_selected_topics = set(unique_topics).difference(topics) if len(non_selected_topics) == 0: non_selected_topics = [-1] selection = df.loc[df.topic.isin(non_selected_topics), :] selection["text"] = "" selection.loc[len(selection), :] = [ None, None, selection.x.mean(), selection.y.mean(), "Другие документы", ] if False: fig.add_trace( go.Scattergl( x=selection.x, y=selection.y, hovertext=selection.doc if not hide_document_hover else None, hoverinfo="text", mode="markers", name="other", showlegend=False, marker=dict(color="#CFD8DC", size=5, opacity=0.5), ) ) # Selected topics for name, topic in zip(names, unique_topics): if topic in topics and topic != -1: selection = df.loc[df.topic == topic, :] selection["text"] = "" if not hide_annotations: selection.loc[len(selection), :] = [ None, None, selection.x.mean(), selection.y.mean(), name, ] fig.add_trace( go.Scattergl( x=selection.x, y=selection.y, hovertext=selection.doc if not hide_document_hover else None, hoverinfo="text", text=selection.text, mode="markers", name=name, textfont=dict( size=12, ), marker=dict(color="#CFD8DC", size=5, opacity=0.5) if topic == 0 else dict(size=5, opacity=0.5), ) ) # Add grid in a 'plus' shape x_range = ( df.x.min() - abs((df.x.min()) * 0.15), df.x.max() + abs((df.x.max()) * 0.15), ) y_range = ( df.y.min() - abs((df.y.min()) * 0.15), df.y.max() + abs((df.y.max()) * 0.15), ) fig.add_shape( type="line", x0=sum(x_range) / 2, y0=y_range[0], x1=sum(x_range) / 2, y1=y_range[1], line=dict(color="#CFD8DC", width=2), ) fig.add_shape( type="line", x0=x_range[0], y0=sum(y_range) / 2, x1=x_range[1], y1=sum(y_range) / 2, line=dict(color="#9E9E9E", width=2), ) fig.add_annotation(x=x_range[0], y=sum(y_range) / 2, text="D1", showarrow=False, yshift=10) fig.add_annotation(y=y_range[1], x=sum(x_range) / 2, text="D2", showarrow=False, xshift=10) # Stylize layout fig.update_layout( template="simple_white", title={ "text": f"{title}", "x": 0.5, "xanchor": "center", "yanchor": "top", "font": dict(size=22, color="Black"), }, width=width, height=height, ) fig.update_xaxes(visible=False) fig.update_yaxes(visible=False) return fig if False: rgb = [] for i in range((n_topics+1) // 2): h, s, v = i / ((n_topics+1)//2), 1.0, 1.0 r, g, b = colorsys.hsv_to_rgb(h, s, v) y, _, _ = colorsys.rgb_to_yiq(r, g, b) rgb.append([min(1, 0.6/y*r), min(1, 0.6/y*g), min(1, 0.6/y*b)]) rgb = rgb + [[0.5*r, 0.5*g, 0.5*b] for r,g,b in rgb] else: rgb = [[0,0,1], [0,0,0.5], [0.5,0,0], [0.5,0.5,0], [0.5,0.5,0.5], [0,0.5,0], [1,0,1], [1,0,0], [0,0.5,0.5], [0.5,0,0.5]] byword = False markup_cases = ['HMM','SentHMM'] def modernize_text(text_orig): text = text_orig for j in re.finditer('i', text.lower()): k = j.span()[0] if set(text[max(0,k-1):k+2].lower()).intersection('абвгдеёжзийклмнопрстуфхцчшщъыьэюя'): text = text[:k] + ('и' if text[k] == 'i' else 'И') + text[k+1:] text = re.sub('чьк', 'чк', text) text = re.sub('чьт', 'чт', text) text = re.sub('чьп', 'чп', text) text = re.sub('чьв', 'чв', text) text = normalize(text) text = re.sub('кия\\b', 'кие', text) text = re.sub('яго\\b', 'его', text) text = re.sub('\\b([Хх])ороше\\b', r'\1орошо', text) return text def prepare_vocab(norm_cb, lemma_cb, pr_pbar): docs = st.session_state['docs'] for i in range(len(docs)): docs[i]['text'] = modernize_text(docs[i]['text_orig']) if norm_cb else docs[i]['text_orig'] if lemma_cb: if 'analyzer' not in st.session_state: try: st.session_state['analyzer'] = maru.get_analyzer(tagger='rnn', lemmatizer='pymorphy') except: st.session_state['analyzer'] = {'segmenter': Segmenter(), 'morph_vocab': MorphVocab(), 'emb': NewsEmbedding()} st.session_state['analyzer']['morph_tagger'] = NewsMorphTagger(st.session_state['analyzer']['emb']) for i in range(len(docs)): docs[i]['tokens'],docs[i]['input'] = [],[] if not byword: docs[i]['sents'] = [s for s in sentenize(docs[i]['text'])] chunks = [t for t in tokenize(docs[i]['text'])] else: docs[i]['sents'] = [Substring(t.span()[0], t.span()[1], t.group()) for t in re.finditer(token_pattern_filt, docs[i]['text'])] chunks = docs[i]['sents'] if lemma_cb: try: analyzed = st.session_state['analyzer'].analyze([t.text for t in chunks]) lemmas = [morph.lemma for morph in analyzed] except: doc = Doc(docs[i]['text']) doc.segment(st.session_state['analyzer']['segmenter']) doc.tag_morph(st.session_state['analyzer']['morph_tagger']) for token in doc.tokens: token.lemmatize(st.session_state['analyzer']['morph_vocab']) chunks = [Substring(token.start, token.stop, token.text) for token in doc.tokens] lemmas = [token.lemma for token in doc.tokens] else: lemmas = [t.text.lower() for t in chunks] j = 0 for s in docs[i]['sents']: tokens = [] while j < len(chunks) and chunks[j].start < s.stop: tokens.append(Substring(chunks[j].start, chunks[j].stop, lemmas[j])) j += 1 docs[i]['tokens'].append(tokens) docs[i]['input'].append(''.join(s.text + ' ' for s in tokens)) docs[i]['input'] = ''.join(s for s in docs[i]['input']) pr_pbar.progress((i+1)/len(docs), text=f'Подготовка словаря: {i+1}/{len(docs)}') st.session_state['docs'] = docs vectorizer = CountVectorizer(token_pattern=token_pattern_filt, stop_words=st.session_state['stopwords']) st.session_state['counts'] = vectorizer.fit_transform([d['input'] for d in docs]).toarray() st.session_state['words'] = vectorizer.get_feature_names_out() words = st.session_state['words'] vocab = {w: i for i,w in enumerate(words)} for i in range(len(docs)): docs[i]['seq'] = [] docs[i]['parts'] = [] for j in range(len(docs[i]['tokens'])): docs[i]['tokens'][j] = [t for t in docs[i]['tokens'][j] if t.text in vocab] add = [vocab[t.text] for t in docs[i]['tokens'][j]] if add: docs[i]['seq'] += add docs[i]['parts'] += [j for _ in range(len(add))] docs[i]['seq'] = np.array(docs[i]['seq'], dtype='int') if not byword: docs[i]['parts'] = np.array(docs[i]['parts'], dtype='int') else: docs[i]['parts'] = np.arange(len(docs[i]['parts']), dtype='int') docs[i]['tokens'] = sum((t for t in docs[i]['tokens']), []) st.session_state['ready'] = '' #with open('words.txt', encoding='utf-8') as f: # words0 = f.read().split('\n') #print(set(words) - set(words0)) st.session_state['navec'] = Navec.load('./src/navec_hudlit_v1_12B_500K_300d_100q.tar') word_embs = np.full((len(words), 300), np.nan) for i,word in enumerate(words): if word in st.session_state['navec']: word_embs[i] = st.session_state['navec'][word] mask = ~np.any(np.isnan(word_embs), axis=1) st.session_state['word_sims'] = np.full((len(words), len(words)), np.nan) st.session_state['word_sims'][np.ix_(mask,mask)] = 1 - cosine_distances(word_embs[mask]) dist = 10 min_count = 10 idxs = np.where(st.session_state['counts'].sum(axis=0) >= min_count)[0] npmi = np.zeros((len(idxs), len(idxs))) probs = {} seq = np.hstack([d['seq'] for d in docs]) p = (2*dist - 1) / max(1, len(seq)) pos = [np.where(seq == i)[0] for i in idxs] for i1 in range(len(idxs)): pr_pbar.progress((i1+1)/len(idxs), text=f'Обработка коллокаций: {i1+1}/{len(idxs)}') v1 = pos[i1] for i2 in range(i1+1,len(idxs)): v2 = pos[i2] k = min(len(v1),len(v2)) d = np.abs(v1[...,np.newaxis] - v2) < dist if d.any(): if k > 1: rows,cols = linear_sum_assignment(d, maximize=True) num = d[rows,cols].sum() else: num = np.any(d).astype(int) if num > 0: m = max(len(v1),len(v2)) if (k,m,num) not in probs: val = 0 q = 1 - (1-p) ** m for n in range(num, k+1): val += (q ** n) * ((1-q) ** (k-n)) * math.comb(k, n) probs[(k,m,num)] = max(0, 1-val) npmi[i1,i2] = probs[(k,m,num)] npmi[i2,i1] = npmi[i1,i2] st.session_state['word_dist'] = dist st.session_state['word_min_count'] = min_count st.session_state['npmi'] = npmi def perplexity_hmm(start, trans, emis, reduce=True): use_lengths = False if 'use_lengths' not in st.session_state else st.session_state['use_lengths'] docs = st.session_state['docs'] n_topics = len(start) perp = 0 if reduce else [] if st.session_state['ready'] == 'SentHMM': A = start.copy() k = 0 for doc in docs: p = 0 for j in range(len(doc['sents'])): for t in np.where([doc['parts'] == j])[1]: p += np.log(emis[st.session_state['seq'][k], doc['seq'][t]]) p += np.log(A[st.session_state['seq'][k]]) A = trans[st.session_state['seq'][k]] k += 1 if use_lengths: A = start.copy() if reduce: perp += p else: perp.append(p) else: A = start.copy() for doc in docs: p = 0 seq = doc['seq'] parts = doc['parts'] for j in range(len(np.unique(doc['parts']))): if any(parts == j): if parts[0] != j: A = A @ trans for k in np.where([parts == j])[1]: A *= emis[:,seq[k]] q = A.max() A /= q p += np.log(q) p += np.log(A.sum()) if use_lengths: A = start.copy() elif len(seq) > 0: (A / A.sum()) @ trans if reduce: perp += p else: perp.append(p) if reduce: perp = np.exp(-perp / sum(len(d['seq']) for d in docs)) else: perp = np.exp(-np.array(perp) / np.array([max(len(d['seq']),1) for d in docs])) return perp def perplexity(D, phi, theta): temp = np.log(phi @ theta); temp[D == 0] = 0; p = np.exp((-(D * temp).sum()) / D.sum()) return p def correlation(phi): p = np.corrcoef(phi.transpose()) p = (p.sum() - p.shape[0]) / (p.shape[0] * (p.shape[0] - 1)) return p def coherence(phi, topk=10, dist=10): idx0 = np.argsort(-phi, axis=0)[:topk] idx = np.unique(idx0) inv_idx = {j: i for i,j in enumerate(idx)} occur = np.zeros((len(idx),), dtype='int') cooccur = np.zeros((len(idx),len(idx)), dtype='int') seqs = [d['seq'] for d in st.session_state['docs'] if len(d['seq']) > 0] for seq in seqs: ind = np.array([i for i,j in enumerate(idx) if j in seq]) if len(ind) > 0: pos = [np.where(seq == idx[j]) - np.arange(0, max(1, len(seq)-dist+1))[...,np.newaxis] for j in ind] pos = np.vstack([np.any((lambda x: np.logical_and(x >= 0, x < dist))(p), axis=1) for p in pos]) occur[ind] += pos.sum(axis=1) cooccur[np.ix_(ind,ind)] += np.minimum(pos[...,np.newaxis], \ pos.transpose().reshape((1,pos.shape[1],pos.shape[0]))).sum(axis=1) ndocs = sum(max(1, len(seq)-dist+1) for seq in seqs) c = np.log(cooccur) numer = c - np.log(occur * occur[...,np.newaxis] / ndocs) denom = np.log(ndocs) - c npmi = numer / denom npmi[np.isnan(npmi)] = -1 np.fill_diagonal(npmi, 0) p = np.zeros((idx0.shape[1],)) for i in range(idx0.shape[1]): ind = np.array([inv_idx[j] for j in idx0[:,i]]) p[i] = npmi[np.ix_(ind,ind)].sum() / (len(ind) * (len(ind)-1)) p = p.mean() return p def diversity(phi, topk=10): idx = np.argsort(-phi, axis=0)[:topk] p = len(np.unique(idx)) / idx.size return p def fill_result(): topicnames = [] for i in range(st.session_state['phi'].shape[1]): indices = np.argsort(st.session_state['phi' if st.session_state['ready'] != 'BERTopic' else 'ctfidf'][:,i])[::-1] temp = f'' for j in range(7): word = st.session_state['words'][indices[j]] temp += f'{word}, ' topicnames.append(temp[:-2]) st.session_state['topicnames'] = topicnames if st.session_state['ready'] in ['LDA','ARTM','CTM','BERTopic','Top2Vec']: temp = np.log(st.session_state['phi'] @ st.session_state['theta']) D = st.session_state['counts'].transpose() temp[D == 0] = 0 p = np.exp((-(D * temp).sum(axis=0)) / D.sum(axis=0)) p[np.isnan(p)] = 1 st.session_state['perplexity'] = p elif st.session_state['ready'] == 'SentHMM': n_topics = len(st.session_state['start']) theta = np.zeros((n_topics, st.session_state['counts'].shape[0])) phi = np.zeros((st.session_state['counts'].shape[1], theta.shape[0])) labels = [] k = 0 for i,d in enumerate(st.session_state['docs']): add = -np.ones_like(d['parts']) for j in range(len(d['sents'])): add[d['parts'] == j] = st.session_state['seq'][k] k += 1 labels.append(add) theta[:,i] += np.bincount(add, minlength=theta.shape[0]) for a,s in zip(add,d['seq']): phi[s,a] += 1 phi = phi/phi.sum(axis=0) phi[np.isnan(phi)] = 1/phi.shape[0] theta = theta/theta.sum(axis=0) theta[np.isnan(theta)] = 1/theta.shape[0] st.session_state['phi'] = phi st.session_state['theta'] = theta st.session_state['labels'] = labels st.session_state['perplexity'] = perplexity_hmm(st.session_state['start'], st.session_state['trans'], \ st.session_state['phi'].transpose(), reduce=False) elif st.session_state['ready'] == 'HMM': emis = st.session_state['phi'].transpose() n_topics = emis.shape[0] theta = np.zeros((n_topics, st.session_state['counts'].shape[0])) docs = st.session_state['docs'] start = st.session_state['start'] trans = st.session_state['trans'] st.session_state['perplexity'] = perplexity_hmm(start, trans, emis, reduce=False) labels = [[] for _ in range(len(docs))] for i in range(len(docs)): seq = docs[i]['seq'] k = len(seq) if k > 0: P = docs[i]['parts'][:-1] != docs[i]['parts'][1:] A = start * emis[:,seq[0]] M = np.zeros((n_topics, k-1), dtype=int) for j in range(1,k): if P[j-1]: A = A[...,np.newaxis] * trans M[:,j-1] = np.argmax(A, axis=0) A = A.max(axis=0) * emis[:,seq[j]] else: A *= emis[:,seq[j]] M[:,j-1] = np.arange(n_topics) A /= A.max() res = [int(np.argmax(A))] for j in range(k-2,-1,-1): res = [M[res[0],j]] + res labels[i] = np.array(res) theta[:,i] = np.bincount(res, minlength=n_topics) theta = theta / theta.sum(axis=0) theta[np.isnan(theta)] = 1/n_topics st.session_state['labels'] = labels st.session_state['theta'] = theta colors = [WD_COLOR_INDEX.BLUE, WD_COLOR_INDEX.DARK_BLUE, WD_COLOR_INDEX.DARK_RED, WD_COLOR_INDEX.DARK_YELLOW, WD_COLOR_INDEX.GRAY_50, WD_COLOR_INDEX.GREEN, WD_COLOR_INDEX.PINK, WD_COLOR_INDEX.RED, WD_COLOR_INDEX.TEAL, WD_COLOR_INDEX.VIOLET] n_topics = st.session_state['phi'].shape[1] wdoc = Document() for i in range(n_topics): wdoc.add_heading(f'Тема {i+1}', level=1) p = wdoc.add_paragraph() indices = np.argsort(st.session_state['phi'][:,i])[::-1] for j in range(20): word = st.session_state['words'][indices[j]] score = st.session_state['phi'][indices[j],i] r = p.add_run(f'{word} ({score:.4f}) ') r.font.color.rgb = RGBColor(255,255,255) r.font.highlight_color = colors[i] if st.session_state['ready'] in ['HMM', 'SentHMM']: font_size = 10 wdoc.add_heading(f'Таблица переходов', level=1) tab = wdoc.add_table(n_topics+2, n_topics+1) tab.style = 'Table Grid' cell = tab.cell(0, 0) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER cell = tab.cell(1, 0) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER run = par.add_run(f'0') run.bold = True run.font.size = Pt(font_size) for j in range(n_topics): cell = tab.cell(0, j+1) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER val = st.session_state['trans'][i,j] run = par.add_run(f'{j+1}') run.bold = True run.font.size = Pt(font_size) cell = tab.cell(1, j+1) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER val = st.session_state['start'][j] run = par.add_run(f'{val:.4f}') run.font.size = Pt(font_size) for i in range(n_topics): cell = tab.cell(i+2, 0) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER run = par.add_run(f'{i+1}') run.bold = True run.font.size = Pt(font_size) for j in range(n_topics): cell = tab.cell(i+2, j+1) par = cell.paragraphs[0] par.alignment = WD_ALIGN_PARAGRAPH.CENTER val = st.session_state['trans'][i,j] run = par.add_run(f'{val:.4f}') run.font.size = Pt(font_size) if st.session_state['ready'] in markup_cases: docs = st.session_state['docs'] for i in range(len(docs)): wdoc.add_heading(docs[i]['name'], level=1) p = wdoc.add_paragraph() if st.session_state['ready'] not in ['HMM','SentHMM']: prev,k0 = 0,-1 for j,pos in enumerate(st.session_state['docs'][i]['seq']): tok = st.session_state['docs'][i]['tokens'][j] r0 = p.add_run(st.session_state['docs'][i]['text'][prev:tok.start]) temp = st.session_state['phi'][pos,:] * st.session_state['theta'][:,i] k = np.argmax(temp) % len(rgb) r = p.add_run(st.session_state['docs'][i]['text'][tok.start:tok.stop]) if (temp / temp.sum()).max() > 5/n_topics: if k == k0: r0.font.color.rgb = RGBColor(255,255,255) r0.font.highlight_color = colors[k] elif k0 >= 0 and not any(c.isalpha() for c in r0.text): r0.font.color.rgb = RGBColor(255,255,255) r0.font.highlight_color = colors[k0] r.font.color.rgb = RGBColor(255,255,255) r.font.highlight_color = colors[k] k0 = k else: k0 = -1 prev = tok.stop r = p.add_run(st.session_state['docs'][i]['text'][prev:]) else: prev = 0 k = 0 if len(st.session_state['labels'][i]) == 0 else st.session_state['labels'][i][0] for j,sent in enumerate(st.session_state['docs'][i]['sents']): r = p.add_run(st.session_state['docs'][i]['text'][prev:sent.start]) r.font.color.rgb = RGBColor(255,255,255) r.font.highlight_color = colors[k] if any(st.session_state['docs'][i]['parts'] == j): k = mode(st.session_state['labels'][i][st.session_state['docs'][i]['parts'] == j]).mode[0] % len(colors) r = p.add_run(st.session_state['docs'][i]['text'][sent.start:sent.stop]) r.font.color.rgb = RGBColor(255,255,255) r.font.highlight_color = colors[k] prev = sent.stop r = p.add_run(st.session_state['docs'][i]['text'][prev:]) r.font.color.rgb = RGBColor(255,255,255) r.font.highlight_color = colors[k] wdoc.save('Разметка.docx') with open('Разметка.docx','rb') as f: st.session_state['markup'] = f.read() nwt = st.session_state['phi'] * (st.session_state['theta'] * st.session_state['counts'].sum(axis=1)).sum(axis=1) df = pd.DataFrame(nwt / nwt.sum(axis=1)[...,np.newaxis]) df.index = st.session_state['words'] st.session_state['phi_df'] = df st.session_state['phi_df'].columns = np.arange(len(st.session_state['phi_df'].columns))+1 df = pd.DataFrame(st.session_state['theta'].transpose()) df.index = [d['name'] for d in st.session_state['docs']] st.session_state['theta_df'] = df st.session_state['theta_df'].columns = np.arange(len(st.session_state['theta_df'].columns))+1 def init_phi_theta(n_topics, w = 1, random_state=None): counts = st.session_state['counts'] mask = np.any(counts, axis=1) X = counts[mask] / counts[mask].sum(axis=1)[...,np.newaxis] labels = np.zeros((counts.shape[0],), dtype=int) kmeans = SphericalKMeans(n_clusters=n_topics, random_state=random_state).fit(csr_matrix(X)) labels[mask] = kmeans.labels_ if byword: labels[mask] = np.load('labels.npy')[0] sub0 = np.argsort([counts.sum(axis=1)[labels == i].sum() for i in range(n_topics)]) sub = sub0.copy() sub[sub0] = np.arange(n_topics) labels = sub[labels] theta = w * np.ones((n_topics, counts.shape[0])) for i in range(theta.shape[1]): theta[labels[i],i] += 1 phi = (theta @ counts).transpose() phi = phi / phi.sum(axis=0) theta = theta / theta.sum(axis=0) return phi, theta def calc_metrics(counts, phi, theta): corr = correlation(phi) coh = coherence(phi) div = diversity(phi) return ({'Перплексия': perplexity(counts, phi, theta)} if theta is not None else {}) | \ {'Корреляция': corr, 'Когерентность': coh, 'Разнообразие': div} def main(): st.title('Тематическое моделирование') if 'ready' not in st.session_state: st.session_state['ready'] = '' if 'metrics' not in st.session_state: st.session_state['metrics'] = {} with st.sidebar: doc_fn = st.file_uploader('Загрузить документы', type='txt') stop_fn = st.file_uploader('Загрузить стоп-слова', type='txt') norm_cb = st.checkbox('Перевести в современную орфографию', value=True) lemma_cb = st.checkbox('Сделать лемматизацию', value=False) vocab_btn = st.button('Получить словарь') pr_pbar = st.progress(0.0, text='Подготовка словаря:') random_state = st.number_input('Cлучайное зерно', step=1) if doc_fn: if 'doc_id' not in st.session_state or st.session_state['doc_id'] != doc_fn.file_id: st.session_state['doc_id'] = doc_fn.file_id docs = StringIO(doc_fn.getvalue().decode('utf-8')) docs = docs.read() docs = [d.strip('\r').split('\t') for d in docs.split('\n')] docs = [{'name': d[0], 'date': d[1], 'text_orig': d[2]} for d in docs if d[0]] st.session_state['docs'] = docs st.session_state['ready'] = '' st.session_state['words'] = [] else: docs = st.session_state['docs'] else: docs,st.session_state['docs'],st.session_state['words'] = [],[],[] if stop_fn: if 'stop_id' not in st.session_state or st.session_state['stop_id'] != stop_fn.file_id: st.session_state['stop_id'] = stop_fn.file_id stopwords = StringIO(stop_fn.getvalue().decode('utf-8')) stopwords = stopwords.read().split() st.session_state['stopwords'] = stopwords st.session_state['ready'] = '' else: stopwords = st.session_state['stopwords'] else: stopwords,st.session_state['stopwords'] = [],[] if vocab_btn and docs: prepare_vocab(norm_cb, lemma_cb, pr_pbar) col1, col2 = st.columns([0.33, 0.67]) with col1: tab1,tab2,tab3,tab4,tab5,tab6 = st.tabs(['LDA','ARTM','HMM','CTM','BERTopic','Top2Vec']) with tab1: # LDA n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10) theta_reg = st.slider('Коэффициент сглаживания тем в документах', min_value=0.01, max_value=1.0, value=0.1, step=0.01) phi_reg = st.slider('Коэффициент сглаживания слов в темах', min_value=0.01, max_value=1.0, value=0.1, step=0.01) max_iter = st.slider('Число итераций', min_value=1, max_value=1000, value=100) empty_msg = 'Извлечение тем \nПолная перплексия \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы') and docs: tm_pbar = st.progress(0.0, text=empty_msg) if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) lda = LatentDirichletAllocation(n_components=n_topics, doc_topic_prior=theta_reg, topic_word_prior=phi_reg, \ max_iter=max_iter, evaluate_every=1, perp_tol=0.01, verbose=True, random_state=random_state) sys.stdout_old = sys.stdout sys.stdout = StringIO() t = Thread(target=lda.fit_transform, args=(st.session_state['counts'],)) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stdout.getvalue() m = re.findall('iteration: ([0-9]+) of max_iter: ([0-9]+), perplexity: ([0-9]+.?[0-9]+)', text) if m: phi = lda.components_.transpose() phi = phi / phi.sum(axis=0) theta = lda.transform(st.session_state['counts']).transpose() metrics = {'Полная перплексия': float(m[-1][2])} | calc_metrics(st.session_state['counts'].transpose(), phi, theta) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0]}/{m[-1][1]} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar.progress(int(m[-1][0])/int(m[-1][1]), text=st.session_state['progress_msg']) t.join() text = sys.stdout.getvalue() sys.stdout = sys.stdout_old phi = lda.components_.transpose() phi = phi / phi.sum(axis=0) theta = lda.transform(st.session_state['counts']).transpose() m = re.findall('iteration: ([0-9]+) of max_iter: ([0-9]+), perplexity: ([0-9]+.?[0-9]+)', text) metrics = {'Полная перплексия': float(m[-1][2]) if m else 1.0} metrics = metrics | calc_metrics(st.session_state['counts'].transpose(), phi, theta) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0] if m else max_iter}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar.progress(1.0, text=st.session_state['progress_msg']) #theta_emb = PCA(n_components=2).fit_transform(st.session_state['counts']) theta_emb = UMAP(n_components=2).fit_transform(st.session_state['counts']) #theta_emb = TSNE(n_components=2).fit_transform(st.session_state['counts']) st.session_state['theta_emb'] = theta_emb st.session_state['phi'] = phi st.session_state['theta'] = theta st.session_state['ready'] = 'LDA' fill_result() else: tm_pbar = st.progress(1.0 if st.session_state['ready'] == 'LDA' else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] == 'LDA' else empty_msg) with tab2: # ARTM n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10, key='n_topics2') theta_reg = st.slider('Коэффициент сглаживания тем в документах', min_value=-10.0, max_value=10.0, value=0.0, step=0.01, \ key='theta_reg2') phi_reg = st.slider('Коэффициент сглаживания слов в темах',min_value=-10.0, max_value=10.0, value=0.0, step=0.01, key='phi_reg2') decorr_reg = st.slider('Коэффициент контрастирования тем',min_value=0, max_value=100000, value=0, step=100, key='decorr_reg2') emb_reg = st.slider('Коэффициент сглаживания по синонимам',min_value=0, max_value=1000, value=0, step=1, key='emb_reg2') max_iter = st.slider('Число итераций', min_value=1, max_value=1000, value=100, key='max_iter2') empty_msg = 'Извлечение тем \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы', key='run_tm2') and docs: tm_pbar2 = st.progress(0.0, text=empty_msg) if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) word_sims = st.session_state['word_sims'] word_sims = np.nan_to_num(word_sims, nan=0.0) word_sims -= np.diag(word_sims.sum(axis=1)) phi,theta = init_phi_theta(n_topics, w=1, random_state=random_state) metrics = calc_metrics(st.session_state['counts'].transpose(), phi, theta) st.session_state['progress_msg'] = f'Извлечение тем: 0/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar2.progress(0.0, text=st.session_state['progress_msg']) part = 100 for i in range(max_iter): phi0 = np.zeros_like(phi) theta0 = np.zeros_like(theta) for a in range((phi.shape[0]-1) // part + 1): it = [a*part, min((a+1)*part, phi.shape[0])] phi1 = phi[it[0]:it[1],:] for b in range((theta.shape[1]-1) // part + 1): jt = [b*part, min((b+1)*part, theta.shape[1])] theta1 = theta[:,jt[0]:jt[1]] ntdw = phi1[...,np.newaxis] * theta1.reshape((1,) + theta1.shape) ntdw = ntdw / ntdw.sum(axis=1)[:,np.newaxis,:] ntdw[np.isnan(ntdw)] = 1/n_topics ntdw = st.session_state['counts'][jt[0]:jt[1],it[0]:it[1]].transpose()[:,np.newaxis,:] * ntdw phi0[it[0]:it[1],:] += ntdw.sum(axis=2) theta0[:,jt[0]:jt[1]] += ntdw.sum(axis=0) #ntdw = phi[...,np.newaxis] * theta.reshape((1,) + theta.shape) #ntdw = ntdw / ntdw.sum(axis=1)[:,np.newaxis,:] #ntdw[np.isnan(ntdw)] = 1/n_topics #ntdw = st.session_state['counts'].transpose()[:,np.newaxis,:] * ntdw phi = phi0 + phi_reg if decorr_reg > 0 or emb_reg > 0: phi0 = phi0 / phi0.sum(axis=0) phi0[np.isnan(phi0)] = 1/phi0.shape[0] if decorr_reg > 0: phi -= decorr_reg*phi0*(phi0.sum(axis=1)[...,np.newaxis]-phi0) if emb_reg > 0: #phi = (emb_reg * word_sims @ phi0 + phi0) / (emb_reg * word_sims.sum(axis=1)[...,np.newaxis] + 1) phi += emb_reg * phi0 * (word_sims @ phi0) phi = np.maximum(phi, 0) phi = phi / phi.sum(axis=0) phi[np.isnan(phi)] = 1/phi.shape[0] theta = np.maximum(theta0 + theta_reg, 0.0) theta = theta / theta.sum(axis=0) theta[np.isnan(theta)] = 1/theta.shape[0] metrics = calc_metrics(st.session_state['counts'].transpose(), phi, theta) st.session_state['progress_msg'] = f'Извлечение тем: {i+1}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar2.progress((i+1)/max_iter, text=st.session_state['progress_msg']) st.session_state['theta_emb'] = UMAP(n_components=2).fit_transform(st.session_state['counts']) st.session_state['phi'] = phi st.session_state['theta'] = theta st.session_state['ready'] = 'ARTM' fill_result() else: tm_pbar2 = st.progress(1.0 if st.session_state['ready'] == 'ARTM' else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] == 'ARTM' else empty_msg) with tab3: # HMM n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10, key='n_topics3') theta_reg = st.slider('Коэффициент сглаживания тем в документах', min_value=-10.0, max_value=10.0, value=0.0, step=0.01, \ key='theta_reg3') phi_reg = st.slider('Коэффициент сглаживания слов в темах', min_value=-10.0, max_value=10.0, value=0.0, step=0.01, \ key='phi_reg3') mean_len = st.slider('Средняя длина темы', min_value=2, max_value=100, value=10) max_iter = st.slider('Число итераций', min_value=0, max_value=1000, value=100, key='max_iter3') use_prev = st.checkbox('Стартовать с предыдущего результата', value=True, key='use_prev3') empty_msg = 'Извлечение тем \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы', key='run_tm3') and docs: tm_pbar3 = st.progress(0.0, text=empty_msg) if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) if st.session_state['ready'] in ['LDA','ARTM','HMM'] and use_prev: phi,theta = st.session_state['phi'],st.session_state['theta'] else: phi,theta = init_phi_theta(n_topics, w=1, random_state=random_state) emis = phi.transpose() if st.session_state['ready'] == 'HMM' and use_prev: start = st.session_state['start'] trans = st.session_state['trans'] else: start = np.ones((n_topics, )) / n_topics trans = np.ones((n_topics, n_topics)) + ((mean_len-1)*(n_topics-1)-1)*np.eye(n_topics) trans = trans / trans.sum(axis=1) use_lib = False st.session_state['use_lengths'] = True if use_lib: model = CategoricalHMM(n_components=n_topics, verbose=True, n_iter=max_iter, algorithm='map', init_params='') model.startprob_ = start model.transmat_ = trans model.emissionprob_ = emis model.startprob_prior = theta_reg + 1 model.transmat_prior = theta_reg + 1 model.emissionprob_prior = phi_reg + 1 sys.stderr_old = sys.stderr sys.stderr = StringIO() t = Thread(target=model.fit, args=(np.hstack(d['seq'] for d in docs).reshape(-1,1), \ [len(d['seq']) for d in docs if len(d['seq']) > 0])) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stderr.getvalue() m,r = None,iter(text.split('\n')[::-1]) while (s := next(r, None)) is not None and not m: m0 = re.findall('([0-9]+)[ ]+([+-]?[0-9]+.?[0-9]+)', s) if m0: m = m0 if m: metrics = {'Полная перплексия': -float(m[-1][1])} metrics['Перплексия'] = perplexity_hmm(model.startprob_, model.transmat_, model.emissionprob_) metrics = metrics | calc_metrics(st.session_state['counts'].transpose(), model.emissionprob_.transpose(), None) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0]}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar3.progress(int(m[-1][0])/max_iter, text=st.session_state['progress_msg']) t.join() text = sys.stderr.getvalue() sys.stderr = sys.stderr_old m,r = None,iter(text.split('\n')[::-1]) while (s := next(r, None)) is not None and not m: m0 = re.findall('([0-9]+) ([+-]?[0-9]+.?[0-9]+)', s) if m0: m = m0 metrics = {'Полная перплексия': -float(m[-1][1]) if m else 1} metrics['Перплексия'] = perplexity_hmm(model.startprob_, model.transmat_, model.emissionprob_) metrics = metrics | calc_metrics(st.session_state['counts'].transpose(), model.emissionprob_.transpose(), None) emis = model.emissionprob_ start = model.startprob_ trans = model.transmat_ st.session_state['progress_msg'] = f'Извлечение тем: {max_iter}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar3.progress(1.0, text=st.session_state['progress_msg']) for i in range(max_iter if not use_lib else 0): start0 = np.zeros_like(start) trans0 = np.zeros_like(trans) emis0 = np.zeros_like(emis) p = 0 for pos in range(len(docs)): seq = docs[pos]['seq'] if seq.size > 0: P = docs[pos]['parts'][:-1] != docs[pos]['parts'][1:] k = len(seq) V = np.zeros((k, emis.shape[1])) V[np.arange(k), seq] = 1 A = np.zeros((n_topics, k)) B = np.zeros((n_topics, k)) H = np.zeros((n_topics, n_topics, k)) for j in range(k): if j == 0: A[:,0] = start * emis[:,seq[0]] elif P[j-1]: A[:,j] = (A[:,j-1] @ trans) * emis[:,seq[j]] else: A[:,j] = A[:,j-1] * emis[:,seq[j]] q = A[:,j].max() A[:,j] /= q p += np.log(q) p += np.log(A[:,-1].sum()) B[:,-1] = 1 for j in range(k-2,-1,-1): if P[j]: B[:,j] = trans @ (B[:,j+1] * emis[:,seq[j+1]]) else: B[:,j] = B[:,j+1] * emis[:,seq[j+1]] B[:,j] /= B[:,j].max() G = A * B; G /= G.sum(axis=0) for j in range(k-1): if P[j]: H[:,:,j] = (A[:,j:j+1] @ B[:,j+1:j+2].transpose()) * trans * emis[:,seq[j+1]:seq[j+1]+1].transpose() H[:,:,j] /= H[:,:,j].sum() start0 += G[:,0] trans0 += H.sum(axis=2) emis0 += G @ V start0 += theta_reg trans0 += theta_reg start = start0 / start0.sum() trans = trans0 / trans0.sum(axis=1)[...,np.newaxis] emis = np.maximum(0, emis0 + phi_reg) emis /= emis.sum(axis=1)[...,np.newaxis] emis[np.isnan(emis)] = 1 / emis.shape[1] metrics = {'Перплексия': np.exp(-p / sum([len(d['seq']) for d in docs]))} metrics = metrics | calc_metrics(st.session_state['counts'].transpose(), emis.transpose(), theta=None) st.session_state['progress_msg'] = f'Извлечение тем: {i+1}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar3.progress((i+1)/max_iter, text=st.session_state['progress_msg']) st.session_state['theta_emb'] = UMAP(n_components=2).fit_transform(st.session_state['counts']) st.session_state['phi'] = emis.transpose() st.session_state['start'] = start st.session_state['trans'] = trans st.session_state['ready'] = 'HMM' fill_result() else: tm_pbar3 = st.progress(1.0 if st.session_state['ready'] == 'HMM' else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] == 'HMM' else empty_msg) with tab4: # CTM n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10, key='n_topics4') max_iter = st.slider('Число итераций', min_value=1, max_value=1000, value=100, key='max_iter4') model_names = ['deeppavlov/rubert-base-cased-sentence', 'ai-forever/sbert_large_mt_nlu_ru', 'ai-forever/sbert_large_nlu_ru', 'ai-forever/ru-en-RoSBERTa', 'ai-forever/FRIDA']; model_name = st.selectbox("Выберите модель", model_names, key='model_name4') qt = TopicModelDataPreparation(model_name, max_seq_length = 512) empty_msg = 'Извлечение тем \nОшибка \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы', key='run_tm4') and docs: if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) tm_pbar4 = st.progress(0.0, text='Подготовка данных') #training_dataset = qt.fit(text_for_contextual=[d['text'] for d in docs], \ # text_for_bow=[d['input'] for d in docs]) sys.stderr_old = sys.stderr sys.stderr = StringIO() vectorizer = CountVectorizer(token_pattern=token_pattern_filt, stop_words=st.session_state['stopwords']) t = ReturnableThread(target=tm_data_prep_fit, args=(qt, [d['text'] for d in docs], [d['input'] for d in docs], vectorizer)) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stderr.getvalue() m = re.findall('([0-9]+)/([0-9]+)', text) if m: tm_pbar4.progress(min(1.0, (int(m[-1][0])+1)/int(m[-1][1])), text=f'Подготовка данных: {int(m[-1][0])+1}/{m[-1][1]}') t.join() training_dataset = t.result if m: tm_pbar4.progress(1.0, text=f'Подготовка данных: {int(m[-1][1])+1}/{m[-1][1]}') tm_pbar4.progress(0.0, text=empty_msg) st.session_state['topic_model'] = CombinedTM(bow_size=len(qt.vocab), contextual_size=768, n_components=n_topics, \ num_data_loader_workers=0, num_epochs=max_iter) sys.stderr_old = sys.stderr sys.stderr = StringIO() t = Thread(target=st.session_state['topic_model'].fit, args=(training_dataset,)) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stderr.getvalue() m,r = None,iter(text.split('\r')[::-1]) while (s := next(r, None)) is not None and not m: m0 = re.findall('Epoch: \[([0-9]+)/([0-9]+)\].*Train Loss: ([0-9]+.?[0-9]+)', s) if m0: m = m0 if m: #lda_vis_data = st.session_state['topic_model'].get_ldavis_data_format(qt.vocab, training_dataset, n_samples=10000) #theta = lda_vis_data['doc_topic_dists'].transpose() #phi = torch.softmax(20*st.session_state['topic_model'].best_components, axis=1).detach().cpu().numpy().transpose() metrics = {'Ошибка': float(m[-1][2])} #| calc_metrics(st.session_state['counts'].transpose(), phi, theta) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0]}/{m[-1][1]} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar4.progress(int(m[-1][0])/int(m[-1][1]), text=st.session_state['progress_msg']) t.join() st.session_state['lda_vis_data'] = st.session_state['topic_model'].get_ldavis_data_format(qt.vocab, training_dataset, \ n_samples=10000) st.session_state['theta'] = st.session_state['lda_vis_data']['doc_topic_dists'].transpose() st.session_state['phi'] = torch.softmax(20*st.session_state['topic_model'].best_components, \ axis=1).detach().cpu().numpy().transpose() #phi = (st.session_state['theta'] @ st.session_state['counts']).transpose() text = sys.stderr.getvalue() sys.stderr = sys.stderr_old m,r = None,iter(text.split('\r')[::-1]) while (s := next(r, None)) is not None and not m: m0 = re.findall('Epoch: \[([0-9]+)/([0-9]+)\].*Train Loss: ([0-9]+.?[0-9]+)', s) if m0: m = m0 metrics = {'Ошибка': float(m[-1][2]) if m else 0.0} | calc_metrics(st.session_state['counts'].transpose(), \ st.session_state['phi'], st.session_state['theta']) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0] if m else max_iter}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar4.progress(1.0, text=st.session_state['progress_msg']) st.session_state['ready'] = 'CTM' fill_result() else: tm_pbar4 = st.progress(1.0 if st.session_state['ready'] == 'CTM' else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] == 'CTM' else empty_msg) with tab5: # BERTopic n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10, key='n_topics5') n_mix = st.slider('Число гауссиан', min_value=1, max_value=10, value=3, key='n_mix5') mean_len = st.slider('Средняя длина темы', min_value=2, max_value=100, value=5, key='mean_len5') max_iter = st.slider('Число итераций', min_value=0, max_value=1000, value=0, key='max_iter5') model_names = ['deeppavlov/rubert-base-cased-sentence', 'ai-forever/sbert_large_mt_nlu_ru', 'ai-forever/sbert_large_nlu_ru', 'ai-forever/ru-en-RoSBERTa', 'ai-forever/FRIDA']; model_name = st.selectbox("Выберите модель", model_names, key='model_name5') empty_msg = 'Извлечение тем \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы', key='run_tm5') and docs: tm_pbar5 = st.progress(0.0, text=empty_msg) if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) tm_pbar5.progress(1/4, text=f'Загрузка модели: 1/4') embedding_model = SentenceTransformer(model_name) try: max_length = embedding_model[0].auto_model.embeddings.position_embeddings.num_embeddings embedding_model[0].max_seq_length = max_length embedding_model.tokenizer.model_max_length = max_length except: pass vectorizer_model = CountVectorizer(stop_words=stopwords, token_pattern=token_pattern_filt) topic_model = BERTopic(embedding_model=embedding_model, vectorizer_model=vectorizer_model, nr_topics=n_topics, \ min_topic_size=2 if max_iter > 0 else n_mix) topic_model.embedding_model = select_backend(topic_model.embedding_model, language=topic_model.language, \ verbose=topic_model.verbose) topic_model.umap_model.random_state = random_state st.session_state['topic_model'] = topic_model tm_pbar5.progress(2/4, text=f'Вычисление эмбеддингов документов: 2/4') embeddings = st.session_state['topic_model']._extract_embeddings([d['text'] for d in docs], method="document") st.session_state['embeddings'] = embeddings tm_pbar5.progress(3/4, text=f'Понижение размерности эмбеддингов: 3/4') umap_model = UMAP(n_neighbors=10, n_components=2, min_dist=0.0, metric="cosine", random_state=random_state) umap_model.fit(embeddings) st.session_state['reduced_embeddings'] = umap_model.embedding_ tm_pbar5.progress(4/4, text=f'Извлечение тем: 4/4') topics, probs = st.session_state['topic_model'].fit_transform([d['text'] for d in docs], embeddings=embeddings) n_topics = np.unique(topics).size st.session_state['use_lengths'] = False if max_iter > 0: if False: tm_pbar5.progress(0.0, text=f'Вычисление эмбеддингов предложений: 0/{max_iter}') embs = [] for i in range(len(docs)): embs.append(st.session_state['topic_model'].embedding_model.embed([s.text for s in docs[i]['sents']])) tm_pbar5.progress((i+1)/len(docs), text=f'Вычисление эмбеддингов предложений: {i+1}/{len(docs)}') embs = np.vstack(embs) tm_pbar5.progress(1/2, text=f'Понижение размерности эмбеддингов: 1/2') st.session_state['embeddings_sent'] = st.session_state['topic_model'].umap_model.transform(embs) st.session_state['reduced_embeddings_sent'] = umap_model.transform(embs) embs = st.session_state['embeddings_sent'] lengths = [len(d['sents']) for d in docs] idxs = np.hstack([(t+1)*np.ones(len(d['sents']),dtype='int') for d,t in \ zip(docs,st.session_state['topic_model'].topics_)]) tm_pbar5.progress(2/2, text=f'Вычисление начального приближения гауссиан: 2/2') gmms = [GaussianMixture(n_mix).fit(embs[idxs == i]) for i in range(idxs.max()+1)] weights = np.stack([gmm.weights_ for gmm in gmms]) means = np.stack([gmm.means_ for gmm in gmms]) covars = np.stack([gmm.covariances_ for gmm in gmms]) start = np.ones((n_topics, )) / n_topics trans = np.ones((n_topics, n_topics)) + ((mean_len-1)*(n_topics-1)-1)*np.eye(n_topics) trans = trans / trans.sum(axis=1) use_lib = False if use_lib: gmmhmm = GMMHMM(n_components=phi.shape[1], n_mix=n_mix, covariance_type='full', algorithm='map', \ n_iter=max_iter, verbose=True, init_params='', random_state=random_state) gmmhmm.weights_ = weights gmmhmm.means_ = means gmmhmm.startprob_ = start gmmhmm.transmat_ = trans sys.stderr_old = sys.stderr sys.stderr = StringIO() t = Thread(target=gmmhmm.fit, args=(embs, lengths if st.session_state['use_lengths'] else None)) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stderr.getvalue() m,r = None,iter(text.split('\n')[::-1]) while (s := next(r, None)) is not None and not m: m0 = re.findall('([0-9]+)[ ]+([+-]?[0-9]+.?[0-9]+)', s) if m0: m = m0 if m: metrics = {'Полная перплексия': -float(m[-1][1])} st.session_state['progress_msg'] = f'Подгонка кластеров: {m[-1][0]}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar5.progress(int(m[-1][0])/max_iter, text=st.session_state['progress_msg']) t.join() text = sys.stderr.getvalue() sys.stderr = sys.stderr_old st.session_state['seq'] = gmmhmm.decode(embs, lengths if st.session_state['use_lengths'] else None)[1] st.session_state['start'] = gmmhmm.startprob_ st.session_state['trans'] = gmmhmm.transmat_ else: lengths = np.cumsum([0] + (lengths if st.session_state['use_lengths'] else [sum(lengths)])) emis = np.stack([np.exp(gmm.score_samples(embs)) for gmm in gmms]) for i in range(max_iter): start0 = np.zeros_like(start) trans0 = np.zeros_like(trans) emis0 = np.zeros_like(emis) p = 0 for idx0,idx1 in zip(lengths,lengths[1:]): k = idx1-idx0 A = np.zeros((n_topics, k)) B = np.zeros((n_topics, k)) H = np.zeros((n_topics, n_topics, k)) for j in range(k): if j == 0: A[:,0] = start * emis[:,j+idx0] else: A[:,j] = (A[:,j-1] @ trans) * emis[:,j+idx0] q = A[:,j].max() A[:,j] /= q p += np.log(q) p += np.log(A[:,-1].sum()) B[:,-1] = 1 for j in range(k-2,-1,-1): B[:,j] = trans @ (B[:,j+1] * emis[:,j+idx0]) B[:,j] /= B[:,j].max() G = A * B; G /= G.sum(axis=0) for j in range(k-1): H[:,:,j] = (A[:,j:j+1] @ B[:,j+1:j+2].transpose()) * trans * emis[:,idx0+j+1:idx0+j+2].transpose() H[:,:,j] /= H[:,:,j].sum() start0 += G[:,0] trans0 += H.sum(axis=2) emis0[:,idx0:idx1] = G metrics = {'Перплексия': np.exp(-p/lengths[-1])} st.session_state['progress_msg'] = f'Подгонка кластеров: {i+1}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar5.progress((i+1)/max_iter, text=st.session_state['progress_msg']) for t in range(n_topics): gmmfit(gmms[t], embs, emis0[t]) start = start0 / start0.sum() trans = trans0 / trans0.sum(axis=1)[...,np.newaxis] emis = np.stack([np.exp(gmm.score_samples(embs)) for gmm in gmms]) seq = [] for idx0,idx1 in zip(lengths,lengths[1:]): k = idx1-idx0 A = start * emis[:,idx0] M = np.zeros((n_topics, k-1), dtype=int) for j in range(1,k): A = A[...,np.newaxis] * trans M[:,j-1] = np.argmax(A, axis=0) A = A.max(axis=0) * emis[:,idx0+j] A /= A.max() res = [int(np.argmax(A))] for j in range(k-2,-1,-1): res = [M[res[0],j]] + res seq += res st.session_state['seq'] = np.array(seq) st.session_state['start'] = start st.session_state['trans'] = trans st.session_state['ready'] = 'SentHMM' fill_result() if use_lib: metrics = {'Полная перплексия': -float(m[-1][1]) if m else 1.0} metrics = metrics | {'Перплексия': perplexity_hmm(st.session_state['start'], st.session_state['trans'], \ st.session_state['phi'].transpose())} metrics = metrics | calc_metrics(st.session_state['counts'].transpose(), st.session_state['phi'], None) st.session_state['progress_msg'] = f'Извлечение тем: {m[-1][0] if use_lib and m else max_iter}/{max_iter} \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar5.progress(1.0, text=st.session_state['progress_msg']) else: theta = np.zeros((n_topics, len(topics))) for i in range(len(topics)): if topics[i] == -1: theta[0,i] = 1 else: theta[:,i] = (1-probs[i]) / (n_topics-1) theta[topics[i]+1,i] = probs[i] phi = (theta @ st.session_state['counts']).transpose() phi = phi / phi.sum(axis=0) counts0 = st.session_state['counts'] topics = np.array(st.session_state['topic_model'].topics_) counts = np.vstack([counts0[topics == i-1].sum(axis=0) for i in range(n_topics)]) ratio = counts.sum() / counts.sum(axis=0) scores = (counts/counts.sum(axis=1)[...,np.newaxis]) * np.log(1 + ratio/phi.shape[1]) topic_repr = {i-1: [(st.session_state['words'][j], scores[i,j]) for j in np.argsort(scores[i])[::-1][:10]] \ for i in range(n_topics)} st.session_state['ctfidf'] = scores.transpose() st.session_state['topic_model'].topic_representations_ = topic_repr st.session_state['phi'] = phi st.session_state['theta'] = theta st.session_state['ready'] = 'BERTopic' fill_result() metrics = calc_metrics(st.session_state['counts'].transpose(), st.session_state['phi'], st.session_state['theta']) st.session_state['progress_msg'] = f'Извлечение тем: 1/1 \n' + \ ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar5.progress(1.0, text=st.session_state['progress_msg']) else: tm_pbar5 = st.progress(1.0 if st.session_state['ready'] == ['BERTopic','SentHMM'] else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] in ['BERTopic','SentHMM'] else empty_msg) with tab6: # Top2Vec n_topics = st.slider('Число тем', min_value=2, max_value=100, value=10, key='n_topics6') min_cluster_size = st.slider('Размер кластера', min_value=2, max_value=100, value=15, key='min_cluster_size6') min_count = st.slider('Минимальное число вхождений слова', min_value=1, max_value=100, value=10, key='min_count6') model_names = ['deeppavlov/rubert-base-cased-sentence', 'ai-forever/sbert_large_mt_nlu_ru', 'ai-forever/sbert_large_nlu_ru', 'ai-forever/ru-en-RoSBERTa', 'ai-forever/FRIDA', 'doc2vec' ]; model_name = st.selectbox("Выберите модель", model_names, key='model_name6') empty_msg = 'Извлечение тем \nПерплексия \nКорреляция \nКогерентность \nРазнообразие' if st.button('Извлечь темы', key='run_tm6') and docs: tm_pbar6 = st.progress(0.0, text='Извлечение тем') if 'words' not in st.session_state or len(st.session_state['words']) == 0: prepare_vocab(norm_cb, lemma_cb, pr_pbar) umap_args = {'n_neighbors': 15, 'n_components': 5, 'metric': 'cosine', 'random_state': random_state} hdbscan_args = {'min_cluster_size': min_cluster_size, 'metric': 'euclidean', 'cluster_selection_method': 'eom'} def tokenizer(doc): return [w for w in re.findall(re.compile(token_pattern_filt), doc.lower()) if w not in stopwords] documents = [d['input'] for d in docs] if True: sys.stderr_old = sys.stderr sys.stderr = StringIO() t = ReturnableThread(target=Top2VecNew, kwargs={'embedding_model': model_name, 'documents': documents, \ 'umap_args':umap_args, 'hdbscan_args':hdbscan_args, \ 'tokenizer':tokenizer,'min_count':min_count}) t.start() while t.is_alive(): time.sleep(0.1) text = sys.stderr.getvalue() desc = ['Pre-processing documents for training', 'Downloading', 'Creating joint document/word embedding', 'Creating lower dimension embedding of documents', 'Finding dense areas of documents', 'Finding topics'] msg = ['Предобработка документов для обучения', 'Загрузка модели', 'Создание совместного эмбеддинга документов/словаря', 'Создание эмбеддингов документов низшей размерности', 'Поиск областей документов с высокой плотностью', 'Извлечение тем'] pos = [text.find(d) for d in desc] if max(pos) >= 0: idx = pos.index(max(pos)) tm_pbar6.progress((idx+1)/6, f'{msg[idx]} ({idx+1}/6)') t.join() st.session_state['topic_model'] = t.result sys.stderr = sys.stderr_old else: st.session_state['topic_model'] = Top2VecNew(embedding_model=model_name, documents=documents, umap_args=umap_args, \ hdbscan_args=hdbscan_args, tokenizer=tokenizer,min_count=min_count) if n_topics < len(st.session_state['topic_model'].get_topics()[2]): st.session_state['topic_model'].hierarchical_topic_reduction(n_topics) doc_top = st.session_state['topic_model'].doc_top_reduced topic_word_scores = st.session_state['topic_model'].topic_word_scores_reduced topic_words = st.session_state['topic_model'].topic_words_reduced else: n_topics = len(st.session_state['topic_model'].get_topics()[2]) doc_top = st.session_state['topic_model'].doc_top topic_word_scores = st.session_state['topic_model'].topic_word_scores topic_words = st.session_state['topic_model'].topic_words phi = -10*np.ones((st.session_state['counts'].shape[1], n_topics)) theta = np.zeros((n_topics, st.session_state['counts'].shape[0])) for i,t in enumerate(doc_top): theta[t,i] = 1 for i in range(topic_word_scores.shape[0]): for j in range(topic_word_scores.shape[1]): idx = np.where(st.session_state['words'] == topic_words[i][j])[0] if idx: phi[idx[0],i] = topic_word_scores[i,j] phi = scipy.special.softmax(phi, axis=0) umap_args = {'n_neighbors': 15, 'n_components': 2, 'min_dist': 0.0, 'metric': 'cosine'} embeddings_2d = UMAP(**umap_args).fit_transform(st.session_state['topic_model']._get_document_vectors(norm=False)) st.session_state['topic_model'].embeddings_2d = embeddings_2d st.session_state['phi'] = phi st.session_state['theta'] = theta st.session_state['ready'] = 'Top2Vec' fill_result() metrics = calc_metrics(st.session_state['counts'].transpose(), st.session_state['phi'], st.session_state['theta']) st.session_state['progress_msg'] = f'Извлечение тем: 1/1 \n' + ''.join(f'{k}: {v:.4f} \n' for k,v in metrics.items()) tm_pbar6.progress(1.0, text=st.session_state['progress_msg']) else: tm_pbar6 = st.progress(1.0 if st.session_state['ready'] == 'Top2Vec' else 0.0, \ text=st.session_state['progress_msg'] if st.session_state['ready'] == 'Top2Vec' else empty_msg) with col2: tab21,tab22,tab23,tab24,tab25,tab26 = \ st.tabs(['Темы','Хронология слов','Хронология тем','Кластеризация','Слова по темам','Темы по документам']) with tab21: # Темы if len(st.session_state['words']) > 0: words = st.session_state['words'].tolist() col21a1,col21a2 = st.columns([0.23,0.77]) with col21a1: if st.session_state['ready']: topic_sizes = 100*st.session_state['theta'].sum(axis=1) / st.session_state['theta'].shape[1] topicnames = [f'Тема {i+1} ({topic_sizes[i]:.2f}%)' \ for i,_ in enumerate(st.session_state['topicnames'])] + ['Перплексия','По порядку'] sub = topicnames.index(st.radio('Темы', topicnames)) else: topicnames = ['По частоте','По алфавиту'] sub = topicnames.index(st.radio('Слова', topicnames)) n_topics = len(topicnames) - 2 with col21a2: par = f'
' for i in range(n_topics): par += f"""{tn}
""" st.markdown(par, unsafe_allow_html=True) col21b1,col21b2,col21b3 = st.columns([0.35,0.3,0.35]) with col21b1: if sub < n_topics: scores = st.session_state['phi' if st.session_state['ready'] != 'BERTopic' else 'ctfidf'][:,sub] word_idxs = np.argsort(-scores) elif sub == n_topics: scores = st.session_state['counts'].sum(axis=0) word_idxs = np.argsort(-scores) else: scores = st.session_state['counts'].sum(axis=0) word_idxs = np.arange(len(scores)) if sub < n_topics: options = [f'{words[i]} ({scores[i]:.4f})' for i in word_idxs] else: options = [f'{words[i]} ({scores[i]})' for i in word_idxs] st.selectbox('Слова темы', options) with col21b2: mask = st.session_state['counts'].sum(axis=0) >= st.session_state['word_min_count'] words_filt = [f'{s}' for s in st.session_state['words'][mask].tolist()] word = st.selectbox('Коллокант', words_filt) with col21b3: values = st.session_state['npmi'][words_filt.index(word)].squeeze() cands = zip(words_filt, values) cands = sorted(cands, key = lambda x: -x[1]) st.selectbox('Коллокатор', [f'{w} ({v:.4f})' for w,v in cands]) options = st.session_state['words'].tolist() filters = st.multiselect(f'Словарь корпуса ({len(options)})', options) if sub < n_topics: scores = st.session_state['theta'][sub,:] elif sub == n_topics: scores = st.session_state['perplexity'] if st.session_state['ready'] else -np.arange(len(docs)) else: scores = -np.arange(len(docs)) doc_idxs = np.argsort(-scores) filt_idxs = [words.index(f) for f in filters] doc_idxs = [i for i in doc_idxs if st.session_state['counts'][i,filt_idxs].all()] if sub <= n_topics and st.session_state['ready']: docnames = [docs[i]['name'] + f' ({scores[i]:.4f})' for i in doc_idxs] else: docnames = [docs[i]['name'] for i in doc_idxs] col21c1,col21c2 = st.columns([0.85,0.15]) with col21c1: docname = st.selectbox(f'Отфильтрованные документы ({len(docnames)})', docnames) if docname: idx = docnames.index(docname) idx = doc_idxs[idx] else: idx = None with col21c2: if st.session_state['ready'] in markup_cases: st.download_button('Скачать разметку', data=st.session_state['markup'], file_name='Разметка.docx') else: st.button('Скачать разметку', disabled=True) par = f'' if not st.session_state['ready'] and idx is not None: par += st.session_state['docs'][idx]['text'] if st.session_state['ready'] and st.session_state['ready'] not in ['HMM','SentHMM'] and idx is not None: prev,k0 = 0,-1 for i,pos in enumerate(st.session_state['docs'][idx]['seq']): tok = st.session_state['docs'][idx]['tokens'][i] add0 = st.session_state['docs'][idx]['text'][prev:tok.start] temp = st.session_state['phi'][pos,:] * st.session_state['theta'][:,idx] k = np.argmax(temp) % len(rgb) add = st.session_state['docs'][idx]['text'][tok.start:tok.stop] if (temp / temp.sum()).max() > 5/n_topics: if k == k0: par += f"""{add0+add}""" else: if k0 >= 0 and not any(c.isalpha() for c in add0): par += f"""{add0}""" else: par += f"""{add0}""" par += f"""{add}""" k0 = k else: par += f"""{add0+add}""" k0 = -1 prev = tok.stop add = st.session_state['docs'][idx]['text'][prev:] par += f"""{add}""" elif st.session_state['ready'] and idx is not None: prev = 0 k = 0 if len(st.session_state['labels'][idx]) == 0 else st.session_state['labels'][idx][0] for i,sent in enumerate(st.session_state['docs'][idx]['sents']): add = st.session_state['docs'][idx]['text'][prev:sent.start].replace('.','\.') par += f"""{add}""" if any(st.session_state['docs'][idx]['parts'] == i): k = mode(st.session_state['labels'][idx][st.session_state['docs'][idx]['parts'] == i]).mode[0] % len(rgb) add = st.session_state['docs'][idx]['text'][sent.start:sent.stop].replace('.','\.') par += f"""{add}""" prev = sent.stop add = st.session_state['docs'][idx]['text'][prev:] par += f"""{add}""" st.markdown(par, unsafe_allow_html=True) with tab22: # Хронология слов if len(st.session_state['words']) > 0: dates = [d['date'] for d in st.session_state['docs']] dates = [datetime.date(int(d[6:]),int(d[3:5]),int(d[0:2])) for d in dates] span = [d.toordinal() for d in dates] x = np.arange(span[0], span[-1]+1) xt = [datetime.date.fromordinal(k) for k in x] options = [f'{w} ({c})' for w,c in zip(st.session_state['words'].tolist(), st.session_state['counts'].sum(axis=0))] filters = st.multiselect(f'Словарь корпуса ({len(options)})', options, key='multiselect22') filt_idxs = [options.index(f) for f in filters] fig,ax = plt.subplots() for idx in filt_idxs: y = np.bincount(span-x[0], weights=st.session_state['counts'][:,idx]) y = gaussian_filter1d(y, sigma=10) ax.plot(xt, y) ax.legend([st.session_state['words'][idx] for idx in filt_idxs], fontsize=7.5) ax.tick_params(axis='both', which='major', labelsize=7.5) ax.tick_params(axis='both', which='minor', labelsize=6) st.pyplot(fig) with tab23: # Хронология тем if st.session_state['ready']: n_topics = st.session_state['phi'].shape[1] data = np.zeros(st.session_state['phi'].shape) fig, axs = plt.subplots((n_topics + 1)//2, 2, figsize=(13, (n_topics+1)//2*4)) counts = st.session_state['counts'].sum(axis=1) top = (counts * st.session_state['theta']).max() for i in range(n_topics): val = counts * st.session_state['theta'][i] tn = st.session_state['topicnames'][i] y = np.bincount(span-x[0], weights=val) y = gaussian_filter1d(y, sigma=10) if n_topics > 2: axs[i//2, i%2].tick_params(axis='both', which='major', labelsize=10) axs[i//2, i%2].tick_params(axis='both', which='minor', labelsize=8) #axs[i//2, i%2].set_ylim(0, top) axs[i//2, i%2].set_title(f'Тема {i}: {tn}') axs[i//2, i%2].plot(xt, y, color=rgb[i]) else: axs[i].tick_params(axis='both', which='major', labelsize=10) axs[i].tick_params(axis='both', which='minor', labelsize=8) #axs[i].set_ylim(0, top) axs[i].set_title(f'Тема {i}: {tn}') axs[i].plot(xt, y, color=rgb[i]) st.pyplot(fig) with tab24: # Кластеризация if st.session_state['ready'] in ['LDA','ARTM','HMM']: topicnames = [tn[:[a for a in re.finditer(',',tn)][2].span()[0]] for tn in st.session_state['topicnames']] color_discrete_map={f'{topicnames.index(t)+1}: {t}':rgb2hex(np.array(r)) for t,r in zip(topicnames, rgb)} df = pd.DataFrame(np.hstack([st.session_state['theta_emb'],np.argmax(st.session_state['theta'], axis=0)[...,np.newaxis]]),\ columns=['x','y','z']) df['text'] = [d['text'] for d in docs] df.sort_values(by='z', inplace=True) df['z'] = df['z'].apply(lambda x: f'{int(x)+1}: {topicnames[int(x)]}') df['text'] = df['text'].apply(lambda x: x[:100]) fig = px.scatter( df, x='x', y='y', hover_name='text', color='z', height=640, #color_discrete_map=color_discrete_map, ) fig.update_traces(hovertemplate="%{hovertext}") # Display the chart in Streamlit st.plotly_chart(fig) if st.session_state['ready'] == 'CTM': ctm_pd = vis.prepare(**st.session_state['lda_vis_data'], n_jobs=1) vis.display(ctm_pd) html_string = vis.prepared_data_to_html(ctm_pd) # with open('vis.html','w') as f: # f.write(html_string) components.v1.html(html_string, width=800, height=800, scrolling=True) if st.session_state['ready'] == 'BERTopic': fig = visualize_documents(st.session_state['topic_model'], [d['text'] for d in docs], topics = np.array(st.session_state['topic_model'].topics_) + 1, embeddings=st.session_state['embeddings'], reduced_embeddings=st.session_state['reduced_embeddings']) st.plotly_chart(fig) #fig.write_html('topics.html') if st.session_state['ready'] == 'SentHMM': #topics = [0] #for d,l in zip(docs,st.session_state['labels']): # for i in range(len(d['sents'])): # topics.append(mode(l[d['parts'] == i]).mode[0] if np.any(d['parts'] == i) else topics[-1]) #topics = topics[1:] topics = st.session_state['seq'] fig = visualize_documents(st.session_state['topic_model'], sum(([s.text for s in d['sents']] for d in docs), []), topics = topics, embeddings=st.session_state['embeddings_sent'], reduced_embeddings=st.session_state['reduced_embeddings_sent']) st.plotly_chart(fig) #fig.write_html('topics.html') if st.session_state['ready'] == 'Top2Vec': if st.session_state['topic_model'].doc_top_reduced is not None: df = pd.DataFrame(np.hstack([st.session_state['topic_model'].embeddings_2d, \ st.session_state['topic_model'].doc_top_reduced[...,np.newaxis]]), \ columns=['x','y','z']) else: df = pd.DataFrame(np.hstack([st.session_state['topic_model'].embeddings_2d, \ st.session_state['topic_model'].doc_top[...,np.newaxis]]), \ columns=['x','y','z']) topicnames = [tn[:[a for a in re.finditer(',',tn)][2].span()[0]] for tn in st.session_state['topicnames']] color_discrete_map={f'{topicnames.index(t)+1}: {t}':rgb2hex(np.array(r)) for t,r in zip(topicnames, rgb)} df['text'] = [d['text'] for d in docs] df.sort_values(by='z', inplace=True) df['z'] = df['z'].apply(lambda x: f'{int(x)+1}: {topicnames[int(x)]}') df['text'] = df['text'].apply(lambda x: x[:100]) fig = px.scatter( df, x='x', y='y', hover_name='text', color='z', height=640, #color_discrete_map=color_discrete_map, ) fig.update_traces(hovertemplate="%{hovertext}") st.plotly_chart(fig) with tab25: # Слова по темам if st.session_state['ready'] and 'phi_df' in st.session_state: st.dataframe(st.session_state['phi_df'].style.format(precision=4), height=1024) with tab26: # Темы по документам if st.session_state['ready'] and 'theta_df' in st.session_state: st.dataframe(st.session_state['theta_df'].style.format(precision=4), height=1024) if __name__ == '__main__': try: main() except SystemExit: pass