使用 Dify 和 MoonShot API 做一个懒人 AI 阅读工具(二):轻量 RAG 应用

news2024/11/26 18:38:41

这篇文章,我们继续聊聊,如何折腾 AI 应用,把不 AI 的东西,“AI 起来”。

在不折腾复杂的检索系统的前提下,做一些轻量的 RAG 实践。

写在前面

我从“机器之心会员通讯”上线以来就在订阅,相比较即时的行业资讯报道,这种经过整理的,有一定资料性质的内容,更适合我的胃口。

我订阅的周更内容

但是,人工智能领域发展既蓬勃又迅速,机器之心的记者们每篇会员通讯都会写的满满当当,这当然很棒(买家角度超值)。

看看右上角的绝望的滚动条

但是,在我要消化或者查找具体内容的时候,常常会陷入迷茫,这么多字,咋整。仿佛记者和编辑们整理素材时的痛苦,无损转移到了我阅读理解的时候。

我的读者应该都知道在当前 LLM 模型“横行霸道”的时刻,RAG (增强检索)方案可以借助模型的内容理解和语言重生成能力来解决问题。不过,多数的实现方案中,我们需要折腾 embedding model、需要折腾文档内容抽取、需要折腾向量检索召回内容排序,确保我们想要的内容和模型处理的内容是对的上的,完整一套都整下来的话,这不就复杂了嘛

能够动态查询知识库内容的简单应用

上面是我最终折腾的结果,借助大模型编写了 120 行左右的代码,配合 Dify 默认功能实现的,开箱即用,没有任何复杂的处理过程。

Dify 的知识库功能

虽然在 Dify 中,它默认支持了知识库功能,主打的就是上面的场景,适配的是大量(十万、百万、千万级别以上)的内容,在这个场景下,折腾上面的套路收益是比较大的。但是,如果我们就只想针对百十来篇,几千篇明确的内容进行解析,其实有更简单的方法。

相关代码开源在 soulteary/dify-simple-rag-with-wp,有需要自取。

准备工作

本篇文章的准备工作和上一篇《使用 Dify 和 Moonshot API 构建你的 AI 工作流(一):让不 AI 的应用 AI 化》没有太大差别,想要顺滑的复现本文的结果,你有个 Docker 环境就好啦。

Docker 运行环境

想顺滑的完成实践,我推荐你安装 Docker,不论你的设备是否有显卡,都可以根据自己的操作系统喜好,参考这两篇来完成基础环境的配置《基于 Docker 的深度学习环境:Windows 篇》、《基于 Docker 的深度学习环境:入门篇》。当然,使用 Docker 之后,你还可以做很多事情,比如:之前几十篇有关 Docker 的实践,在此就不赘述啦。

至于应用的初始化,包括 Dify 和用到的 WordPress 应用的初始化,Moonshot API 在 Dify 中的初始化,参考上一篇文章可以非常简单的搞定,这里就不赘述啦。

下面,我们开始非常容易上手的实战内容。

初始化用于处理内容的模型 API

我这里使用的模型还是月之暗面的模型,不过因为本文需要处理的文章内容比较长,有几万字,所以我选择的是 128K 上下文长度的模型。

这样可以让我避免进行常规的文章切片、向量转换和存储操作。最快的把一个非 AI 应用/文章,变的可以 AI 式交互起来。

步骤一:初始化一篇知识库内容

为了本文的内容安全,我们不聊如何通过程序批量的搬运资料文章到本地。重点聊聊如何简单的制作可以和 AI 交互的文章 Bot。

手动初始化一篇知识库内容

至于 AI 处理的文本素材,我们可以通过下面的方式,手动的进行处理,这个步骤大家应该都蛮熟悉的:打开你想要“AI 处理、AI 对话的文档”,全选文本和图片、复制、粘贴到 WordPress 的“新建文章”编辑器中。

快速得到内容的本地快照

操作完毕,包括图片在内的内容就都被搬运到你的本地的、简单的 WordPress 知识库里啦。(这一步,你可以通过程序来完成)

包含文章 ID 的链接

点击文章发布按钮,我们能够得到保存就绪的本地素材文章的访问链接,默认情况下会包含文章的 ID。例如:http://localhost:8083/?p=95

包含文章 ID 的链接

在之前文章《把 WordPress 变成 BaaS 服务:API 调用指南》我们提到过将 WordPress 进行 API 调用的方法,结合文中的方法,我们只需要将 URL 地址改为 http://localhost:8083/?rest_route=/wp/v2/posts/95,并在地址中拼合好上一步得到的文章 ID 就能够使用结构化接口的形式来获取某个具体知识库素材文章的内容啦。

步骤二:使用程序获取素材文章内容

你可以询问各种 Chat 模型,如何编写一个简单的程序,来获取上面 API 中的文章数据,得到类似下面的一段功能性代码。我习惯使用 Go,大概 40 行左右就可以搞定需求:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

type Article struct {
	Content struct {
		Rendered string `json:"rendered"`
	} `json:"content"`
}

func main() {
	client := &http.Client{}
	req, err := http.NewRequest("GET", "http://localhost:8083/?rest_route=/wp/v2/posts/95", nil)
	if err != nil {
		log.Fatal(err)
	}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	var article Article
	err = json.Unmarshal(bodyText, &article)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(article.Content.Rendered)
}

如果我们执行程序,将会得到我们初始化好的机器之心的素材文章的“HTML代码”,在 WordPress 中,为了保证我们的文章格式和原始内容一致,默认会存储内容的 HTML 格式。

这个格式虽然保障了本地的文章内容和原始内容的样式一致,但是因为混杂了 HTML 语言标签,在我们后续使用模型进行内容推理计算的时候,其实会浪费很多不必要的时间(处理的内容更多),变相的浪费我们的模型调用费用(Token 计费)。

为了解决这个问题,我们可以补几行简单的代码,让获取到的内容变成 字数更少、结构更清晰的 Markdown 格式:

html := article.Content.Rendered
converter := md.NewConverter("", true, nil)

markdown, err := converter.ConvertString(html)
if err != nil {
	log.Fatal(err)
}
fmt.Println(markdown)

当然,因为这篇文章没有涉及到文章内图片内容解析,相关的内容其实都是“无效内容”。所以,我们还可以再做一步数据清理的操作,将获取到的 Markdown 数据中的图片信息都先剔除掉:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"

	md "github.com/JohannesKaufmann/html-to-markdown"
)

type Article struct {
	Content struct {
		Rendered string `json:"rendered"`
	} `json:"content"`
}

func GetArticle(id int) (string, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:8083/?rest_route=/wp/v2/posts/%d", id), nil)
	if err != nil {
		return "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var article Article
	err = json.Unmarshal(bodyText, &article)
	if err != nil {
		return "", err
	}
	return article.Content.Rendered, nil
}

func GetMarkdown(html string) (string, error) {
	converter := md.NewConverter("", true, nil)
	return converter.ConvertString(html)
}

func RemoveImages(input string) string {
	return regexp.MustCompile(`!\[.*?\]\((.*?)\)`).ReplaceAllString(input, "")
}

func main() {
	html, err := GetArticle(95)
	if err != nil {
		log.Fatal(err)
	}

	markdown, err := GetMarkdown(html)
	if err != nil {
		log.Fatal(err)
	}

	result := RemoveImages(markdown)
	fmt.Println(result)
}

好了,到这里,获取文章内容并进行基础的“数据处理”就完成啦。我们来继续完成 Dify 相关的自动化过程。

步骤三:在 Dify 中定义一个知识问答机器人应用

目前的新版本 Dify,支持创建四种应用,分别是:聊天助手、文本生成应用、Agent、AI 工作流。

创建一个对话型 AI 应用

我们先来创建一个对话型应用,来满足上文提到的,可以实现“使用自然语言和文章对话”的功能,名字和描述可以根据自己的心情来,随便起。

初始化应用的 Prompt

在完成应用创建后,我们来简单写一段 Prompt,来告诉模型,未来要做的事情是什么。我这里使用的 Prompt 是:

你是 AI 垂直领域内容平台机器之心的首席记者,擅长解读内容中的要点,始终使用简洁的列表形式回答用户的问题。
可爱一些。

## 待处理内容

`markdown
{{article}}
`

## 输出结果

初始化 Dify Prompt 变量

我这里的 Prompt 包含了一个包裹在 markdown 块代码中的变量:article用来动态填充我们的知识库文章素材,让模型未来在对话的上下文中能够找到事实知识,而避免让它自己“脑补内容”,糊弄我们。

步骤四:通过“内容处理钩子”来打通 RAG 流程

激活“内容审查”功能

在 Dify 的流程处理过程中,有一个功能叫做“内容审查”。我个人认为这个中文翻译太糟糕了,或许翻译成 “内容前后处理” 更妥当,因为这个功能远不是只能对接几个云端或者端侧运行的内容安全接口那么简单,还可以做内容增强处理,来改善 Dify 本身对 Prompt 内容的控制能力

Dify 默认能够使用“内容审查功能”,来对提示词和用户输入内容进行修改或者对模型的输出结果进行修改或者过滤。

如果我们在用户输入的时候,根据用户传输的“搜索关键词”,来找到对应的文章素材,然后装填回 Prompt 中,大模型在有针对性的精准回复的时候,就能够获取到必要的知识啦。

设置 API 扩展,而非内容审查

激活内容审查后,我们不需要去设置 “OpenAI 内容审查”、“关键词审查”,而是选择配置 “API 扩展” 能力。关于 API 扩展的实现,我会在下一小节展开,先保持好奇心,继续往下看。

配置 API 扩展接口

默认情况下,我们的列表中没有任何接口可以选择,我们可以随便写一个接口地址,稍晚些时候再来实现就好。(反正有 AI 帮忙写代码

配置完成内容“前置处理钩子”

配置完毕之后,就会出现前面的“API扩展列表选择”的界面,勾选这个 API,然后打开“审查输入内容”,所有的设置就完毕了。

好了,让我们进行最后的步骤,将上面的一切内容,想要学习了解的文章素材、设置好的模型提示词的 AI 应用都关联起来。

步骤五:实现 Dify API 扩展,激活对 Prompt 提示词的前置修改能力

了解了实现原理之后,我们来实现一个 Dify 扩展 API 应用,并且将上文中的素材文章数据获取相关的能力也揉在一起,代码很简单,算行空行只有 121 行:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strconv"

	md "github.com/JohannesKaufmann/html-to-markdown"
	"github.com/gin-gonic/gin"
)

type Article struct {
	Content struct {
		Rendered string `json:"rendered"`
	} `json:"content"`
}

func GetArticle(id int) (string, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", fmt.Sprintf("http://10.11.12.90:8083/?rest_route=/wp/v2/posts/%d", id), nil)
	if err != nil {
		return "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var article Article
	err = json.Unmarshal(bodyText, &article)
	if err != nil {
		return "", err
	}
	return article.Content.Rendered, nil
}

func GetMarkdown(html string) (string, error) {
	converter := md.NewConverter("", true, nil)
	return converter.ConvertString(html)
}

func RemoveImages(input string) string {
	return regexp.MustCompile(`!\[.*?\]\((.*?)\)`).ReplaceAllString(input, "")
}

type ExtensionPointRequest struct {
	Point  string `json:"point"`
	Params struct {
		AppID        string                 `json:"app_id"`
		ToolVariable string                 `json:"tool_variable"`
		Inputs       map[string]interface{} `json:"inputs"`
		Query        string                 `json:"query"`
	} `json:"params"`
}

type UserQuery struct {
	Flagged bool                   `json:"flagged"`
	Action  string                 `json:"action"`
	Inputs  map[string]interface{} `json:"inputs"`
	Query   string                 `json:"query"`
}

func main() {
	router := gin.Default()

	router.POST("/new-api-for-dify", func(c *gin.Context) {
		var req ExtensionPointRequest

		if err := c.BindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		articleId, ok := req.Params.Inputs["article"]
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"error": "missing article id"})
			return
		}

		id, err := strconv.Atoi(articleId.(string))
		if err != nil {
			fmt.Println("转换错误:", err)
		} else {
			fmt.Printf("转换成功: %d\n", id)
		}

		html, err := GetArticle(id)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		markdown, err := GetMarkdown(html)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		var userPayload UserQuery
		userPayload.Flagged = true
		userPayload.Action = "overrided"
		userPayload.Inputs = map[string]interface{}{
			"article": RemoveImages(markdown),
		}
		userPayload.Query = req.Params.Query

		c.JSON(http.StatusOK, userPayload)
	})

	router.Run(":8084")
}

将程序运行起来,我们就能够得到一个运行在 localhost:8084 的 Dify API 扩展应用了,然后上面的所有事情都都串在一起啦。

在上面的代码流程中,我们分别根据用户在界面中传递进来的“文章ID”,来从 WordPress 知识库中获取指定 ID 的文章素材,然后将文章素材进行 Markdown 格式的转换处理、去掉文章中的不必要的内容,然后将 AI 应用使用的 Prompt 提示词进行更新,然后将更新后的提示词模版返还给 Dify 应用。

调试界面的应用

之后,Dify 会将新的提示词,带上用户的问题一并送给大模型去处理。我们就能够得偿所愿啦。

快速将 AI 应用集成到现有程序中

当然,如果你想在你现有的程序里使用刚刚制作的 AI 应用,点击发布按钮旁边的下拉按钮,找到“嵌入网站”。我们能够看到三种不同的嵌入方案,包括直接以弹窗或者浏览器插件的形式使用它,在使用的过程中,别忘记修改示例代码中的“API接口地址”为你真实运行 Dify 的地址。

支持连续对话的文章 Bot

除此之外,如果你点击下拉菜单中的“运行”,你将得到一个单独的网页地址,包含一个没有繁杂调试界面的纯净版界面,这个界面中,你可以放心的进行多轮对话。

其他:完善 RAG 检索能力

还记得开头,我们提到的轻量 RAG 应用吧?虽然指定具体知识库,从远端抽取指定数据也可以被称作简化版的检索过程,但是如果每次都需要这样指定,未免也太不智能了。

好像是把大语言模型关进了兔笼。

而想要在这个简单的应用中,完善 “RAG” 过程中的 Retrieval 动作,其实也很简单,我们让这个获取文章素材 ID 的过程能够和搜索动作关联起来即可。

在 WordPress API 中,有一个接口和搜索相关:rest-api/reference/search-results/,除此之外,在召回的搜索结果中,其实有非常多的字段,可以用于排序策略,比如:

  • 内容创建时间
  • 内容的分类
  • 内容的标签
  • 内容的作者
  • 等等

我们可以在得到搜索结果之后,进行排序处理,然后再用上面的代码进行内容的聚合,传递给能够处理大量文本的模型 API。

这段代码的实现和上面没有太大差异,调用“搜索 API”,然后跑一个循环来处理所有的搜索结果。作为一个懒人,我就不贴代码了,感兴趣的同学可以自己练习一下,或者问模型代码该如何生成,应该能够获得比较多的折腾乐趣。

最后

这篇文章就先写到这里,下一篇相关的内容,我们来聊聊适用于更大规模,灵活度更高的 RAG 相关的知识和内容。不过在此之前,我想聊聊这里面的一些核心组件:Embedding Model (选择和增强),以及存储向量的容器,以及容易被忽略的细节,成本、性能、非常实用的全文检索。

我们,下篇文章再见。

–EOF


我们有一个小小的折腾群,里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。

我们在里面会一起聊聊软硬件、HomeLab、编程上、生活里以及职场中的一些问题,偶尔也在群里不定期的分享一些技术资料。

关于交友的标准,请参考下面的文章:

苏洋:致新朋友:为生活投票,不断寻找更好的朋友

当然,通过下面这篇文章添加好友时,请备注实名和公司或学校、注明来源和目的,珍惜彼此的时间 😄

苏洋:关于折腾群入群的那些事


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2024年04月25日
统计字数: 10255字
阅读时间: 21分钟阅读
本文链接: https://soulteary.com/2024/04/25/use-dify-and-moonshot-api-to-build-your-ai-reading-tool-lightweight-rag-app.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1625823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

若依文件下载

后端自带工具controller package com.ruoyi.web.controller.common;import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.Logger…

Python二进制文件转换为文本文件

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在日常编程中,我们经常会遇到需要将二进制文件转换为文本文件的情况。这可能是因…

Java进阶-异常处理

概述 常见运行时异常 直接继承自RuntimeException或者子类,编译阶段不会报错,运行时可能出现的错误 运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误 数组索引越界异常空指针异常类型转换异常数学操作异常数字转…

【C++报错】c++实例类的时候提示已声明所在行数,所属文件不可访问 的解决办法

这里写自定义目录标题 问题重现 问题解决 问题重现 c实例类的时候提示已声明所在行数,所属文件不可访问 问题解决 在此处,将 构造函数 放在public 问题消除

【AMBA Bus ACE 总线 1 -- ACE 总线介绍】

文章目录 Coherency 问题简介ACE 出现背景ACE 总线介绍Acknowledge signalingSnoop Channel Signals Coherency 问题简介 一致性(Coherency)问题通常出现在多处理器系统中,尤其是当多个处理器可能同时访问同一内存位置时。简单来说&#xff…

解决在 Python 数据分析中遇到的 Matplotlib 字体警告问题

当在 Python 数据分析中遇到类似以下警告时: D:\anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:211: RuntimeWarning: Glyph 24037 missing from current font.font.set_text(s, 0.0, flagsflags) D:\anaconda3\lib\site-packages\matplotlib\ba…

【Java EE】总结12种锁策略以及synchronized的实现原理

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

网易云热评加密函数逆向(Jsrpc)

今天给大家来个jsrpc实战教程,让大家继续加深对jsrpc的理解和认识。 1、因为网易云音乐热评的加密并不在cookie上,而是参数加密,所以这里就不需要进行hook住cookie了。 2、之前就知道网易云音乐热评的加密存在之地是在下图的位置,是那个函数window.asrsea(JSON.stringify(…

我成为亚马逊云科技“技领云博主“啦

小李哥前一阵子被选为了大中华区亚马逊云科技”技领云领袖“博主项目首批成员。目前该项目也在公开招募,今天就给大家给大家探秘下该项目~1️⃣什么是”技领☁️领袖“博主项目? 该项目是由亚马逊云科技AWS官方发起的项目,主要是招…

开源代码分享(23)-基于混合整数二阶锥规划(MISOCP)的主动配电网最优潮流计算

参考文献: [1]乔珊. 主动配电网多源协同运行优化研究[D]. 山东大学, 2021. [2]高红均,刘俊勇,沈晓东,等. 主动配电网最优潮流研究及其应用实例 [J]. 中国电机工程学报, 2017, 37 (06): 1634-1645. DOI:10.13334/j.0258-8013.pcsee.152839. 1.引言 主动配电网技术的…

智慧养猪场视频AI智能监控与可视化管理方案

在科技日新月异的今天,智能化、自动化已成为众多行业追求的方向。养猪业作为传统农业的重要组成部分,同样迎来了技术革新的春风。特别是随着人们对食品安全等问题的日益关注,养猪场视频监控监管方案的智能化升级显得尤为重要。 养猪场视频智…

Java基础(运算符)

运算符 运算符和表达式 运算符:对字面量或者变量进行操作的符号 表达式:用运算符把字面量或者变量连接起来,符合java语法的式子就可以称为表达式;不同运算符连接的表达式体现的是不同类型的表达式。 算术运算符(加…

短视频素材哪个软件好?短视频素材那里来?

在当今数字化时代,高质量的视频素材对于提升任何视频项目的吸引力和专业度都至关重要。以下是全球范围内精选的视频素材网站,每个都能为你的视频创作提供独特的视觉支持和灵感。 1. 蛙学府(中国) 主要提供丰富的中文视频素材&…

Centos 5 的yum源

背景 有使用较老的Centos 5 系统内部安装软件无法正常报错,是由于系统叫老yum源存在问题 处理方法 更换下述yum源,可以将其他repo源文件备份移动到其他目录,添加下述源后重新测试 [C5.11-base] nameCentOS-5.11 baseurlhttp://vault.c…

哈希表练习题

前言 本次博客将要写一写,哈希表的一些使用 哈希表主要是一个映射,比如数组就是一个哈希表 是一个整型对应另一个整型,介绍的哈希表还是要以写题目为例 第一题 242. 有效的字母异位词 - 力扣(LeetCode) 直接来看…

vue-manage-system 更新,后台管理系统开发更简单

vue-manage-system 近期进行了一次版本升级,主要是支持了更多功能、升级依赖版本和优化样式,并且上线了官方文档网站,大部分功能都有文档或者使用示例,更加适合新手上手开发,只需要根据实际业务简单修改,就…

【讯为Linux驱动笔记1】申请一个字符设备

Linux下每个设备都需要有一个专属设备号:主设备号 次设备号 【申请字符设备】 主设备号:一类驱动:如:USB驱动 次设备号:这类驱动下的某个设备 如:键盘鼠标 设备号是32位的dev_t类型的,高12位主…

STL_deque_stack_queue

Deque deque容器(双端队列) ​deque是一种双向开口的分段连续线性空间(对外号称连续,使用者无法感知它是分段的)。deque支持从头尾两端进行元素的插入和删除。deque没有容量的概念,因为它是动态地以分段连续空间组合而成的。随时…

同态加密原理解析

目录 1.数学介绍2.使用多项式环进行加密2.1 私钥和公钥的产生2.2 加密2.3 解密 3.同态计算3.1 同态加法3.2 同态乘法 1.数学介绍 同态加密方案基于一个难以计算的问题Ring Learning with Errorsred。这些方案中的数据在加密和未加密时都用多项式表示。 这里举一个简单的多项式…

最详细的 Windows 下 PyTorch 入门深度学习环境安装与配置 GPU 版 土堆教程

最详细的 Windows 下 PyTorch 入门深度学习环境安装与配置 CPU GPU 版 | 土堆教程 Windows 下 PyTorch 入门深度学习环境安装与配置 GPU 版 教程大纲如何使用此教程快速开始版本 Windows下判断有无NVIDIA GPU安装Anaconda作用流程下载安装 Anaconda 创建虚拟环境利用conda或者…