创新实训2024.05.12日志:建立易学知识库

1. 部署向量知识库到生产机

1.1. 基本环境配置

生产机上的环境还没有配好,这里我记录下需要配置的环境。

python 3.11.8

首先python环境最好选用3.11(准确来说支持3.8-3.11的任何release),我选择了python3.11.8

Python Release Python 3.11.8 | Python.orgicon-default.png?t=N7T8http://PY3118

Git

另外我需要在生产机上部署一些我自己写的程序,例如分词程序、建立易学知识库的程序,这就需要拉代码(不然回回拿着硬盘上机房拷代码太繁琐了),因此还得配置好git的环境。

Git - Downloading Package (git-scm.com) icon-default.png?t=N7T8http://Git

1.2. 安装依赖库

创建虚拟环境

这台服务器上还跑着其它的服务,每个服务都有自己的依赖库,为了防止不同环境的依赖冲突,需要配置虚拟环境(python virtual environment)。

有两种方式可以配置,第一种利用IDE,第二种使用命令行:

 利用IDE:File->Settings->Project Interpreter,随后新创建一个虚拟环境(最好不要继承基解释器的包)。

利用命令行:

python -m venv path.to.your.venv

随后激活虚拟环境:

path.to.your.venv/Scripts/activate

 即可进入虚拟环境。

Git - Downloading Package (git-scm.com)

想要退出,deactivate即可。

安装依赖库

到项目根目录下:

pip install -r requirements.txt 
pip install -r requirements_api.txt
pip install -r requirements_webui.txt 

切换torch到Cuda对应版本

由于我们是带着大模型启动的(Model Worker),而配置上是GPU版本的PyTorch。但是Requirements.txt中的torch是CPU版本的。

如果是GPU版本,应该是torch==2.1.2+cu的某个版本。

所以之后我们要手动下载一遍GPU版本,首先先查看本机cuda版本:

找到英伟达控制面板

找到系统信息

随后在组件中查看cuda驱动版本

随后我们就要找到对应的torch版本,先来到torch官网:

PyTorchicon-default.png?t=N7T8https://pytorch.org/

随后选择适合自己机器的选项,例如我是:

  1. Windows系统
  2. Pip包管理
  3. 语言python
  4. cuda 12.2

这样选完之后,即可获得安装对应torch的命令:

利用这个命令安装即可。

现在我们的torch带cuda版本了。

1.3. 运行服务

一行命令启动:

python startup.py -a

2. 分词与文档分割

这个任务主要还是非结构化文本,像之前我们整理出来的json文件,基本上可以不用动。

有哪些非结构化文本呢?主要还是集中于《周易研究》期刊的各个文章。之前我们是对它进行了Self-QA,提取了问答对,形成了json文件。不过Self-QA仅仅是阅读整篇文章后,提出问题并予以解答,可能会遗漏知识。因此我们需要对这部分文档进行分词,向量化,建立索引,并存入知识库。

2.1. 建立工作站

由于文件的存储结构是文件系统中一棵文件树的结构。因此为了防止掉电和宕机,以便于恢复现场,需要先建立工作站文件。

这一部分我复用了之前生成语料的工作站,详情见于:

项目实训2024.04.12日志:Self-QA生成问答对_self-qa论文-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/lyh20021209/article/details/137696063?spm=1001.2014.3001.5501

2.2. 工作流程

简单来说:

  1. 从配置文件中提取源文件路径、目标存储路径、工作站名称、分词参数等
  2. 遍历所有需要分词的源文件根目录
  3. 提取当前路径下的工作站文件,遍历每一条工作任务记录
  4. 如果任务是文件夹,继续向下深度搜索
  5. 如果是文件,则进行分词
    1. 采用langchain的中文递归分词器,根据分词参数与模式串进行分词
    2. 将分词后得到的字符串列表返回
  6. 删除本条工作任务记录
  7. 回溯

获取配置参数

我将需要用到的参数全部配置到了一个json配置文件中:

  1. 其中file_path包括了来源不同的源文件的源路径以及分词后的存储目标路径,还有源路径下的工作站名称。
  2. 此外还有一个failed_path记录处理失败的任务,便于重新执行。
  3. 之后的split_args是分词时的参数
    1. chunk_size是分词的块的大小
    2. overlap_size是分词时的块的重叠区间长度(上限),这样便于留存上下文,更好地检索信息

此时我们就能够提取这些参数,准备深搜源文件:

    text_split_path = sys.argv[1]

    with open(text_split_path, 'r', encoding='utf-8') as f:
        config = json.load(f)

    fp = config['file_path']
    failed_root = config['failed_path']

    split_args = config['split_args']
    chunk = int(split_args['chunk_size'])
    overlap = int(split_args['overlap_size'])

遍历所有需要分词的源文件根目录,因为可能源文件的根目录有多个,例如《周易研究》2020-2023、《周易研究》2008-2015等。

    for root in fp.values():
        src_paths = root['src_paths']
        target_paths = root['target_paths']
        for i in range(len(src_paths)):
            dfs_split(src_paths[i], target_paths[i], work_station=root['WorkStation'])

深搜处理任务

对于每一个路径,提取当前路径下的工作站文件,遍历每一条工作任务记录。如果任务是文件夹,继续向下深度搜索。如果是文件,则进行分词

def dfs_split(path: str, target: str, work_station: str):
    # 获取工作站名称
    ws = os.sep.join([path, work_station])
    # 副本
    ws_tmp = work_station.replace('.txt', '.tmp')
    ws_tmp = os.sep.join([path, ws_tmp])
    # print(f'{ws}\n{ws_tmp}')

    while True:
        with open(ws, 'r+', encoding='utf-8') as file, open(ws_tmp, 'a+', encoding='utf-8') as tmp:
            lines = file.readlines()

            if len(lines) == 0:
                return

            # 读取第一条任务
            task = path + '\\' + lines[0].rstrip()
            # 剩余任务写入副本
            for k in range(1, len(lines)):
                tmp.write(lines[k].rstrip() + '\n')
            print(f'正在处理任务:{task}')

            # 文件夹深搜
            if os.path.isdir(task):
                dfs_split(task, target, work_station)
            else:
                # 分词
                res = do_split(task)
                # 保存
                do_save(target, res, task, lines[0].rstrip())

        if os.path.exists(ws):
            os.remove(ws)
        # 重命名副本覆盖工作站
        os.rename(ws_tmp, ws)

分词器

对于分词,我才用langchain集成的一个递归分词器。

这个api在langchain官网有介绍:

LangChain中文网:500页中文文档教程,助力大模型LLM应用开发从入门到精通icon-default.png?t=N7T8https://www.langchain.com.cn/

因此我根据这个递归字符文本分割器,封装了一个专门用于中文的(也即模式串上更符合汉语特点的)分词器:

    def __init__(
            self,
            separators: Optional[List[str]] = None,
            keep_separator: bool = True,
            is_separator_regex: bool = True,
            **kwargs: Any,
    ) -> None:
        """Create a new TextSplitter."""
        super().__init__(keep_separator=keep_separator, **kwargs)
        self._separators = separators or [
            "\n\n",
            "\n",
            "。|!|?",
            "\.\s|\!\s|\?\s",
            ";|;\s",
            ",|,\s"
        ]
        self._is_separator_regex = is_separator_regex

 在类对象初始化时,我先看用户是否穿了自定义的模式串(因为用户肯定是对待分词文本更了解的那个),如果没有,用一个默认的模式串分隔符。

    def _split_text(self, text: str, separators: List[str]) -> List[str]:
        """Split incoming text and return chunks."""
        final_chunks = []
        # Get appropriate separator to use
        separator = separators[-1]
        new_separators = []
        for i, _s in enumerate(separators):
            _separator = _s if self._is_separator_regex else re.escape(_s)
            if _s == "":
                separator = _s
                break
            if re.search(_separator, text):
                separator = _s
                new_separators = separators[i + 1:]
                break

        _separator = separator if self._is_separator_regex else re.escape(separator)
        splits = _split_text_with_regex_from_end(text, _separator, self._keep_separator)

        # Now go merging things, recursively splitting longer texts.
        _good_splits = []
        _separator = "" if self._keep_separator else separator
        for s in splits:
            if self._length_function(s) < self._chunk_size:
                _good_splits.append(s)
            else:
                if _good_splits:
                    merged_text = self._merge_splits(_good_splits, _separator)
                    final_chunks.extend(merged_text)
                    _good_splits = []
                if not new_separators:
                    final_chunks.append(s)
                else:
                    other_info = self._split_text(s, new_separators)
                    final_chunks.extend(other_info)
        if _good_splits:
            merged_text = self._merge_splits(_good_splits, _separator)
            final_chunks.extend(merged_text)
        return [re.sub(r"\n{2,}", "\n", chunk.strip()) for chunk in final_chunks if chunk.strip()!=""]
  1. 初始化:创建一个空列表final_chunks,用于存储最终的文本块。

  2. 选择分隔符:从separators列表中选择一个分隔符。如果separators的最后一个元素不是空字符串,并且它在文本中存在,则选择它作为分隔符。如果存在多个分隔符,将选择第一个在文本中找到的分隔符,并使用剩余的分隔符进行后续的分割。

  3. 转义分隔符:如果_is_separator_regexFalse,则使用re.escape()对分隔符进行转义,以确保分隔符中的任何特殊字符都被正确处理。

  4. 分割文本:使用_split_text_with_regex_from_end函数(这个函数没有在代码中定义,可能是外部定义的)来根据选定的分隔符分割文本。self._keep_separator参数决定是否保留分隔符。

  5. 合并文本块:遍历分割后的文本块,如果文本块的长度小于self._chunk_size,则将其添加到_good_splits列表中。否则,将_good_splits中的文本块合并,然后添加到final_chunks中,并清空_good_splits以进行下一轮的合并。

  6. 递归分割:如果当前文本块不能直接合并,并且new_separators(剩余的分隔符)不为空,则对当前文本块使用剩余的分隔符进行递归分割。

  7. 最终合并:如果在遍历结束后_good_splits中还有文本块,则将它们合并并添加到final_chunks中。

  8. 清理和返回:最后,使用正则表达式替换连续的换行符(\n),并去除空白字符,然后返回非空的文本块列表。

因此在分词过程中,我们初始化一个类实例,随后把之前获取到的配置参数传入:

def do_split(task: str) -> List[str]:
    global chunk, overlap
    try:
        text = read_docx(task)
        # 调分词器
        t_splitter = ChineseRecursiveTextSplitter(
            keep_separator=True,
            is_separator_regex=True,
            chunk_size=chunk,
            chunk_overlap=overlap
        )

        res = t_splitter.split_text(text)
        return res
    except Exception as e:
        print(f'对{task}进行分词时出现错误:{e}')
        do_fail_rec(task)

 分词效果大致如下:

可以看到,两段相邻分块之间是有重合的部分,这可以更好的保存上下文线索,便于检索。

3. 建立易学知识库

3.1. 去除冗余数据

这里需要说一下,LangChain ChatChat在上传文件时,会根据参数选项自动对文档做分词和向量化。因此我们这里有两条路:

  1. 我们先跑本地脚本做分词,然后在上传时不做分词仅作向量化
  2. 本地仅准备原始文本,然后在上传时分词且做向量化

我最终选择了第二条路。其实就是把原来本地脚本的分词器给去掉,直接将原始文本保存为文本文件。可能会疑惑这一步是不是必要的?其实是的,因为原始的docx的数据格式是open xml,这个里面会有大量不必要的样式、标签、格式等数据,这些数据向量化后可能会污染我们的知识库。

最终本地保存的原始文本文件如下:

其中每一个文件的内容都是完整的原始文本:

3.2. 易学资料批量上传 

易学知识库服务架构

现在我们需要做的就仅剩下将本地文件上传到服务器的向量知识库中了。在此之前我们要先理解这个易学知识库的架构(如下图):

向量知识库是以文件的形式驻留在服务器的文件系统上,服务器开放两个端口,一个跑web应用客户端,另一个跑web应用服务端。前者请求后者向向量知识库添加新的文件。

一般用户是通过浏览器访问web应用客户端,然后通过点击并选择文件进行文件上传的:

 

这个方式有什么不好呢?我们有这样300多个文件 ,一个一个选太慢了。如果是300w个文件呢?

所以我们要支持本地文件批处理上传,就需要在本地拉起一个脚本,把之前准备好的原始文本上传上去。因此我的解决方案是,在这个脚本中直接请求服务端的上传文件的接口,绕过客户端

文件批量上传

首先我们要先知道这个web应用的服务端在哪个端口。config/server_config.py中,有这么一段源码:

# api.py server
API_SERVER = {
    "host": DEFAULT_BIND_HOST,
    "port": 7861,
}

也就是这个服务在7861端口,同时,server/api.py也佐证了这一点:

    parser.add_argument("--host", type=str, default="0.0.0.0")
    parser.add_argument("--port", type=int, default=7861)
    parser.add_argument("--ssl_keyfile", type=str)
    parser.add_argument("--ssl_certfile", type=str)
    # 初始化消息
    args = parser.parse_args()
    args_dict = vars(args)

    app = create_app()

    run_api(host=args.host,
            port=args.port,
            ssl_keyfile=args.ssl_keyfile,
            ssl_certfile=args.ssl_certfile,
            )

 可以看到--port参数给默认值就是7861。因此我们就需要向7861端口发送请求。

请求格式

这就需要看接口源码了,看看他想要什么样的数据格式:

def upload_docs(
        files: List[UploadFile] = File(..., description="上传文件,支持多文件"),
        knowledge_base_name: str = Form(..., description="知识库名称", examples=["samples"]),
        override: bool = Form(False, description="覆盖已有文件"),
        to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),
        chunk_size: int = Form(CHUNK_SIZE, description="知识库中单段文本最大长度"),
        chunk_overlap: int = Form(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
        zh_title_enhance: bool = Form(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
        docs: Json = Form({}, description="自定义的docs,需要转为json字符串",
                          examples=[{"test.txt": [Document(page_content="custom doc")]}]),
        not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库(用于FAISS)"),
) -> BaseResponse:

首先是一个文件的列表,然后是一些参数,例如向量知识库名称,是否覆写已有文件,是否向量化,分词参数等。

因此我们的请求数据也要包含一个文件的二进制数据,还有这些参数。

 工作流程

现在需要做的就是把原始文本上传重塑成这个接口想要的数据格式,然后往7861的/knowledge_base/upload_docs发请求。

我采取的解决方案是,每次请求仅上传一个文件,遍历整个target文件夹下的所有文件,逐个请求上传。

    for root in fp.values():
        src_paths = root['src_paths']
        api_path = root['api_path']

        for i in range(len(src_paths)):
            for fn in os.listdir(src_paths[i]):
                path = os.path.join(src_paths[i], fn)
                request_api(path, api_path)

其中root是我的json配置文件中的json对象,通过py的json库解析成了字典。

 

随后我们构造请求,向web服务端请求上传文件接口:

    form_data = {
        'knowledge_base_name': knowledge_base_name,
        'override': override,
        'to_vector_store': to_vector_store,
        'chunk_size': chunk,
        'chunk_overlap': overlap,
        'zh_title_enhance': zh_title_enhance,
        'docs': None,
        'not_refresh_vs_cache': 'False'
    }

    # 准备请求中的文件参数
    files = {'files': (file_path,open(file_path, 'rb'))}

    # 发送POST请求
    response = requests.post(url, files=files, data=form_data)
    print(f'响应内容为{response.text}')

 注意这里参数的key要和源码中的接口对应的上。

运行结果如下:

1. 脚本

2. 服务端窗口

3. web ui

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算机基础实训总结全文共5页,当前为第1页。计算机基础实训总结全文共5页,当前为第1页。计算机基础实训总结 计算机基础实训总结全文共5页,当前为第1页。 计算机基础实训总结全文共5页,当前为第1页。 计算机基础实训总结(1) 这是我大学以来第三次实训,每次实训我都感觉学到了好多东西!因为是一天到晚的不间断训练,所以记的会非常牢固。不像平时上课,每上两次的理论课却只有45分钟的实际操作。在课上,有老师在前面演示我们都还能跟着做,可轮到我们独立完成的时候,因为实际操作的少,早就忘光了!我很感谢学校有实训这样的安排,把我们这一学期学的东西系统的集中的进行训练,对我们计算机水平的提高发挥着重要作用!还要感谢我们的窦老师,窦老师很温柔,也很有耐心,即使老师讲了很多遍的问题,我们不会,老师还是会走进我们给我们耐心的指导,还给我们讲一些学习计算机的方法,让我们知道自己在哪方面不足,需要加强,也让我们了解到哪些需要认真的学习,那些是重点,不是没有方向的乱学一通,什么也学不好! 经过这次的实训,我真真确确的感受到了计算机在我们生活中工作中的运用,这些软件、程序能让我们提高工作的效率,更直观更便捷的切入主题。这次我们学习的是数据路的原理及应用的各方面知识,由老师带着我们不断操作。accesssXX能有效的组织、管理和共享数据库信息,能把数据库信息与wep结合在一起,实现数据库信息的共享。同实,access概念清晰,简单易学、实用是适合企业管理人员、数据库管-理-员使用的首眩我觉得学习了这个,对我参加工作后制表、创建查询、数据分析和材料演示都有很大的作用,这样,我们能更清计算机基础实训总结全文共5页,当前为第2页。计算机基础实训总结全文共5页,当前为第2页。楚的了解信息并进行分析。 当然,在学习的过程中并不是一帆风顺的,在这之中,因为要操作的东西很多,有时错一步,后面的结果就无法显示,而自己的计算机又太差,根本检查不出来是哪里出了错!这时候,老师都会耐心的过来帮助我,我很感谢窦老师也很感谢学校能安排这么优秀的窦老师来教我们!只是,我们太顽皮,有很多东西老师讲了我们也没能记住,我想在此说一句:老师,您辛苦了,下学期我们一定认认真真的好好学! 一个星期的实训课很快结束了,我发现我对计算机有了新的认识,以前只知道玩游戏、娱乐和简单的应用。通过这次的实训,我了解到,要真真正正的掌握计算机程序还不是一件简单容易的事儿,但真正掌握后,它带个我们的将是无穷的便捷与科技,我喜欢高端便捷的生活。我希望我能做计算机这个万能机器人的主人而不是奴隶,我会努力加油的!感谢学校,感谢老师给我的帮助,让我的思想、技能又上了一个台阶!感谢!加油! 计算机基础实训总结(2) 时间过得真快,转眼间为期一周的实训已经结束。经过这一周的实训练习让我们学到了许多知识,回头想想实训这几天我们确实是有很大收获的。 一周,看似很简短的时间,实际上按小时计算120小时却是个不小的数字,也计算机基础实训总结全文共5页,当前为第3页。计算机基础实训总结全文共5页,当前为第3页。许有些牵强。但是简短的时间仍是有效的,因为在这一周中我们学到了很多东西,并且接触了从没有制作过的文件和一些演示文稿。这一周我们在学习的同时,也进一步懂得了操作的重要性,实训过程中自己有很多的不懂,很多的问题,都是通过老师或同学的帮助完成的,这次实训让我明白了实训的主要目的是让我们同过不断的实习来积累经验,进而才能把书本的知识转换为技能。实践出真理,在这一周的实训确实有些累,不知从哪开始入手,但是累的有价值。学海无涯,有很多很多的东西在向我们招手,等待我们去努力的学习。在以后的工作、生活和学习中,发展自己的优势,弥补自己的不足和缺陷。 我们兴高采烈的进入到我们实训的计算机机房,打开各自的实训电脑,老师发来我们当天的实训内容。在操作中才知道自己会的只有书本上的知识,到实际操作时什么都不会。自己慢慢的从书上找,结果不是操纵不对就是做不出来结果不对,这些问题我和同学还有老师说过,在老师和同学的帮助下我知道自己的不足之处,并且我改正自己的不足并牢记它。 就这样,我们实训了一周,但是收获不小,在实训中改正自己操作中的不足之处,让我知道实际操作并不是那么简单,实际操作是需要牢固的基础知识,两者是不可分开的。 老师布置了一些的作业给我们操练,在老师精心的指导下我已把Excel,word,网络,powerpoint等操作的很好了!现在我们已步入大学,经过半年的在校学习,对电脑还只是初步的认识和理解,但在这学期期间,一直忙于理论知识的学习,计算机基础实训总结全文共5页,当前为第4页。计算机基础实训总结全文共5页,当前为第4页。没能有机会放开课本,真正切身感受计算机魅力,所以在实训之前,电脑对我们来说是比较抽象的,但通过这次实训,我们揭开了她神秘的面
学习计算机应用基础心得   当今社会,到处充满机遇与挑战。知识是我们面对这一切的筹码,而计算机知识则 更为重要。计算机行业是个飞速发展的行业,日新月异,因此,不断加强理论学习,拓展知 识领域,进行知识更新,是我们当前最为迫切的任务,在学习当中我总结了不少的经验,让 我在以后的学习当中受益匪浅。   一、感受与体会   1.基础很重要   实践证明,对文字、表格等的处理都是计算机课程的基础,需要一定的操作桌面的 知识和能力,需要一定的工具操作能力,学好这些是学习计算机的入门,所以尤为重要 !   2.循序渐进   整个学习过程应采用循序渐进的方式,先了解计算机的基本知识,如计算机的起源 、发展、windowsXX、xp的桌面操作、电子表格等,使自己能由浅入深,由简到繁地掌握 他们的使用技术。   3.学以致用   在学习时始终要与实际应用相结合,不要把主要精力花费在各个命令孤立地学习上 ;要把学以致用的原则贯穿整个学习过程,以让自己对命令能有深刻和形象的理解。   4.熟能生巧   word作为文字操作专家,它能使我们更加深入地理解、熟练文字操作的命令。要强 迫自己做几个综合实例,分别详细地进行文字编辑,使自己可以从全局的角度掌握整个 编辑过程,力争使自己学习完word之后就可以投身到实际的工作中去。   二、学习建议   1.常见问题要弄懂。   对于经常出现的问题,要及时解决。如果推脱,那么问题就越堆越多,不利于今后 的学习。   2.有比较,才有鉴别。   容易混淆的命令,要注意使自己弄清它们之间的区别。   3.养成良好习惯。 其次,学习了常用的办公软件,主要有WORD,EXCEL等,以及常用的几种软件的应用技巧 ,同时也学习了一些解决实际应用过程中经常出现的问题的方法,相信这次学习,会让我在 今后的工作中运用电脑时能够得心应手。   通过学习我真正体会到了计算机知识的更新是很快的,随着教育体制的改革和教育理 念的更新,以及信息技术的飞速发展,如何接受新的教育理念,转变我们传统的教育观念, 来充实我们的专业技能,已经成为我们每一个人必须要解决的第一个问题。只有不断地学 习,才能掌握最新的知识,才能在以后把工作做得更好。   经过这次学习计算机基础知识,我真真确确的感受到了计算机在我们生活中工作中 的运用,这些软件、程序能让我们提高工作的效率,更直观更便捷的切入主题。同实概 念清晰,简单易学、实用是适合企业管理人员、数据库管,对我工作后制表、创建查询 、数据分析和材料演示都有很大的作用,这样我们能更清楚的了解信息并进行分析。 现在我发现我对计算机有了新的认识,以前只知道玩游戏、娱乐和简单的应用。通过 这次的实训,我了解到,要真真正正的掌握计算机程序还不是一件简单容易的事儿,但 真正掌握后,它带个我的将是无穷的便捷与科技,我喜欢高端便捷的生活。    ----------------------- 2020国家开放大学计算机应用基础心得实训1全文共2页,当前为第1页。 2020国家开放大学计算机应用基础心得实训1全文共2页,当前为第2页。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值