BERTopic FR:将文本 Package 在悬停注解中

7uzetpgm  于 2个月前  发布在  其他
关注(0)|答案(5)|浏览(36)

你好,很棒的项目!
我注意到当悬停注解变得相当长时,它们的宽度没有限制,变得非常不可用。当我在个人项目中遇到这个问题时,我使用了类似于这样的文本换行,效果很好(这个示例只是为了了解源代码中缺失的内容):

import textwrap
df["wrapped_doc"] = df["doc"].apply(lambda x: '<br>'.join(textwrap.wrap(x, width=60)))

多走一步就是添加一个"header",用于指定悬停时的相关信息,如点索引id、主题的标签等。
你怎么看?
祝你度过愉快的一天
PS:textwrap位于默认的Python库中

lndjwyie

lndjwyie1#

当你使用.visualize_documents可视化主题时,你基本上可以将文档的任何表示传递给函数,因为它们只用于注解(悬停)。所以在将文档传递给函数时,你可以限制它们的长度。
话虽如此,默认的textwrap解决方案确实会更好。特别是因为它是一个默认的Python库,这应该会让事情变得容易得多。我可能唯一的担忧是,当你尝试可视化大型数据集(>100_000)时,textwrap是否足够快。然而,可视化超过100_000个文档通常不建议,所以我不确定这是否实际上是一个问题。
标题绝对是一个好主意。当涉及到定义这些自定义悬停时,Plotly非常挑剔,但它应该有可能向函数提供额外的元数据。
旁注:我尝试过用类似bokeh的东西替换plotly,以便在悬停中也显示图像,但不幸的是,这需要进行重大更改。这将允许在悬停期间显示更多信息。

hs1rzwqc

hs1rzwqc2#

仅供参考:

import textwrap
s = "a"
s_long = s * 10000
%timeit textwrap.wrap(s, width=60)
# 2.77 µs ± 392 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

%timeit textwrap.wrap(s_long, width=60)
# 906 µs ± 23.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
ru9i0ody

ru9i0ody3#

谢谢,这是一个相当长的文本,这种情况不应该经常发生。然而,如果你有百万份这样的文档,那么处理时间将达到约15分钟,这绝对太长了。

gfttwv5a

gfttwv5a4#

我好奇地想知道langchain是如何进行拆分的。
我从他们的文档中复制了一堆代码,并删除了测试中不需要的部分,结果令人满意:

Using langchain on short s:
829 ns ± 44.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Using langchain on long s:
18.7 µs ± 821 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Using textwrap on short s:
2.82 µs ± 270 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Using textwrap on long s:
11.4 ms ± 751 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

(使用ipython script.py运行)

from textwrap import wrap
from typing import (
    AbstractSet,
    Any,
    Callable,
    Collection,
    Dict,
    Iterable,
    List,
    Literal,
    Optional,
    Sequence,
    Tuple,
    Type,
    TypedDict,
    TypeVar,
    Union,
    cast,
)
from IPython import get_ipython
ipython = get_ipython()

def split_text_with_regex(
    text: str, separator: str, keep_separator: bool
) -> List[str]:
    # Now that we have the separator, split the text
    if separator:
        if keep_separator:
            # The parentheses in the pattern keep the delimiters in the result.
            _splits = re.split(f"({separator})", text)
            splits = [_splits[i] + _splits[i + 1] for i in range(1, len(_splits), 2)]
            if len(_splits) % 2 == 0:
                splits += _splits[-1:]
            splits = [_splits[0]] + splits
        else:
            splits = text.split(separator)
    else:
        splits = list(text)
    return [s for s in splits if s != ""]

def merge_splits(splits: Iterable[str], separator: str) -> List[str]:
        # We now want to combine these smaller pieces into medium size
        # chunks to send to the LLM.
        separator_len = len(separator)
        chunk_size = 60

        docs = []
        current_doc: List[str] = []
        total = 0
        for d in splits:
            _len = len(d)
            if (
                total + _len + (separator_len if len(current_doc) > 0 else 0)
                > chunk_size
            ):
                if total > chunk_size:
                    logger.warning(
                        f"Created a chunk of size {total}, "
                        f"which is longer than the specified {chunk_size}"
                    )
                if len(current_doc) > 0:
                    doc = _join_docs(current_doc, separator)
                    if doc is not None:
                        docs.append(doc)
                    # Keep on popping if:
                    # - we have a larger chunk than in the chunk overlap
                    # - or if we still have any chunks and the length is long
                    while total > self._chunk_overlap or (
                        total + _len + (separator_len if len(current_doc) > 0 else 0)
                        > chunk_size
                        and total > 0
                    ):
                        total -= len(current_doc[0]) + (
                            separator_len if len(current_doc) > 1 else 0
                        )
                        current_doc = current_doc[1:]
            current_doc.append(d)
            total += _len + (separator_len if len(current_doc) > 1 else 0)
        doc = _join_docs(current_doc, separator)
        if doc is not None:
            docs.append(doc)
        return docs

def _join_docs(docs: List[str], separator: str) -> Optional[str]:
    text = separator.join(docs)
    text = text.strip()
    if text == "":
        return None
    else:
        return text

if __name__ == "__main__":
    s = "a"
    s2 = s * 100_000

    print("\nUsing langchain on short s:")
    ipython.run_line_magic('timeit', 'merge_splits(split_text_with_regex(s, "<br>", False), "<br>")')

    print("\nUsing langchain on long s:")
    ipython.run_line_magic('timeit', 'merge_splits(split_text_with_regex(s2, "<br>", False), "<br>")')

    
    print("\nUsing textwrap on short s:")
    ipython.run_line_magic('timeit', 'wrap(s, width=60)')

    print("\nUsing textwrap on long s:")
    ipython.run_line_magic('timeit', 'wrap(s2, width=60)')
nzkunb0c

nzkunb0c5#

感谢您的工作,MaartenGr。
啊,幸好我在自己打开之前找到了这个帖子。我也想把文本 Package 起来,因为它会超出屏幕 :)现在我只是在_documents.py的第105行添加了两行代码。
import textwrap df["doc"] = ['<br>'.join(textwrap.wrap(docs[index],width=80)) for index in indices]
也许您在应用之前检查一下文本长度,因为如果文本很长, Package 仍然没有用处 :)

相关问题