2025-06-22 18:54:59
从某种意义上说,推荐系统和搜索引擎对于用户来说是两个互补的工具。搜索引擎满足了用户有明确目的时的主动查找需求,而推荐系统能够在用户没有明确目的的时候帮助他们发现感兴趣的新内容。
基于用户行为分析的推荐算法是个性化推荐系统的重要算法,学术界一般将这种类型的算法称为协同过滤(Collaborative filtering)算法。顾名思义,协同过滤就是指用户可以齐心协力,通过不断地和网站互动,使自己的推荐列表能够不断过滤掉自己不感兴趣的物品,从而越来越满足自己的需求。
用户行为在个性化推荐系统中一般分两种——显性反馈行为(explicit feedback)和隐性反馈行为(implicit feedback)。显示反馈行为是用户主动做的,比如给视频点赞、给书籍打分等等;隐式反馈行为的代表就是用户浏览页面,这种行为显示出来的用户偏好不是那么明显,但是数据量更大。
计算两个用户的兴趣相似程度:给定用户u和用户v,N(u)表示用户u曾经有过正反馈的物品集合,N(v)表示用户v曾经有过正反馈的物品集合。可以使用Jaccard公式计算两个用户的兴趣相似程度
或者使用余弦相似公式计算相似程度
以余弦相似公式为例,假设有用户ABCD,物品abcde,用户喜欢的物品如下
用户 | 物品 a | 物品 b | 物品 c | 物品 d | 物品 e |
---|---|---|---|---|---|
A | ☑️ | ☑️ | ☑️ | ||
B | ☑️ | ☑️ | |||
C | ☑️ | ☑️ | |||
D | ☑️ | ☑️ | ☑️ |
那么我们可以得到用户A和BCD的相似度
具体计算过程以AD的相似度计算为例
{d}
,|{d}| = 1
,所以分子为13 x 3 = 9
,开根号为31 / 3
以上逻辑可以用代码进行实现:
1 |
def similarity(users): |
执行后得到结果如下
A: {"B": 0.4082482904638631, "C": 0.4082482904638631, "D": 0.3333333333333333}B: {"A": 0.4082482904638631, "C": 0.0, "D": 0.4082482904638631}C: {"A": 0.4082482904638631, "B": 0.0, "D": 0.4082482904638631}D: {"A": 0.3333333333333333, "B": 0.4082482904638631, "C": 0.4082482904638631}
据此我们就可以得到各个用户之间的兴趣相似度了。有了用户兴趣的相似度之后,我们可以给用户推荐和他兴趣最相似的K个用户喜欢的物品。我们可以使用如下公式计算用户u对物品i的感兴趣程度
其中,S(u, K)包含和用户u兴趣最接近的K个用户,N(i)是对物品i有过行为的用户集合,wuv是用户u和用户v的兴趣相似度,rvi代表用户v对物品i的兴趣,因为使用的是单一行为的隐反馈数据,所以所有的rvi=1。
具体的逻辑实现如下:
1 |
def recommend(user, users, w, k): |
通过这个代码我们就可以计算得到指定用户的物品推荐程度了。完整的代码如下:
1 |
import json |
执行代码得到的结果如下
A: {"B": 0.4082482904638631, "C": 0.4082482904638631, "D": 0.3333333333333333}B: {"A": 0.4082482904638631, "C": 0.0, "D": 0.4082482904638631}C: {"A": 0.4082482904638631, "B": 0.0, "D": 0.4082482904638631}D: {"A": 0.3333333333333333, "B": 0.4082482904638631, "C": 0.4082482904638631}d: 0.8164965809277261a: 0.4082482904638631c: 0.4082482904638631
由上面的结果我们可以知道,针对用户C
,最推荐的物品是物品d
。
根据上面的例子我们已经简单了解了基于用户的协同过滤算法,不过这种算法存在问题,主要是
因此,在实际的使用中,更常见的是基于物品的协同过滤算法
为了挖掘长尾信息,避免热门物品对推荐产生影响,减小二八定律的出现。可以用如下公式计算物品之间的相似度
分子是同时喜欢物品i和物品j的用户数,分母是喜欢两个物品用户数的并集。为了减小计算量,我们可以构建一个矩阵来存储某个用户喜欢的物品集合。
举个例子,比如用户A喜欢物品 {a, b, d},那我们可以构建如下矩阵
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 0 | 1 | 0 |b | 1 | 0 | 0 | 1 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 1 | 1 | 0 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
因为a、b、d可以组成ab、ad、bd,所以将矩阵中的对应位置都填上1。这是一个用户的物品信息,对于多个用户,只需要把这些矩阵相加即可。例如有5个用户,他们的物品信息和生成的对应物品矩阵如下
用户 1: {a, c, d}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 1 | 1 | 0 |b | 0 | 0 | 0 | 0 | 0 |c | 1 | 0 | 0 | 1 | 0 |d | 1 | 0 | 1 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
用户 2: {b, c, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 0 | 0 |b | 0 | 0 | 1 | 0 | 1 |c | 0 | 1 | 0 | 0 | 1 |d | 0 | 0 | 0 | 0 | 0 |e | 0 | 1 | 1 | 0 | 0 |
用户 3: {a, d, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 1 | 1 |b | 0 | 0 | 0 | 0 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 1 | 0 | 0 | 0 | 1 |e | 1 | 0 | 0 | 1 | 0 |
用户 4: {b, d}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 0 | 0 | 0 | 0 |b | 0 | 0 | 0 | 1 | 0 |c | 0 | 0 | 0 | 0 | 0 |d | 0 | 1 | 0 | 0 | 0 |e | 0 | 0 | 0 | 0 | 0 |
用户 5: {a, b, c, e}
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 1 | 0 | 1 |b | 1 | 0 | 1 | 0 | 1 |c | 1 | 1 | 0 | 0 | 1 |d | 0 | 0 | 0 | 0 | 0 |e | 1 | 1 | 1 | 0 | 0 |
将这5个用户的物品信息相加,得到矩阵
| a | b | c | d | e |----|-----|-----|-----|-----|-----|a | 0 | 1 | 2 | 3 | 2 |b | 1 | 0 | 3 | 2 | 3 |c | 2 | 3 | 0 | 2 | 3 |d | 3 | 2 | 2 | 0 | 2 |e | 2 | 3 | 3 | 2 | 0 |
在这个矩阵中值越高,代表物品的相关度越高。接下来我们将这个规则用代码进行实现
1 |
number = 'number' |
如上我们先计算每个物品各自被用户喜欢的次数,再计算每个物品和其它物品同时被某个用户喜欢的次数,之后根据物品相似度公式即可计算出物品之间的相关性。为了简单起见,如上代码只使用了一个字典变量,物品自己被喜欢的次数被保存在key为number
的字段中,物品和其它物品同时被喜欢的次数则保存在key为其它物品ID的字段中。
有了如上逻辑之后,我们就可以计算物品相似度了,假设有用户如下
{ 'A': {'a', 'b', 'd'}, 'B': {'a', 'c'}, 'C': {'b', 'e', 'a'}, 'D': {'c', 'd', 'e'}}
计算得到的物品相似度
b: {'a': 0.8164965809277261, 'd': 0.5, 'e': 0.5}a: {'b': 0.8164965809277261, 'd': 0.4082482904638631, 'c': 0.4082482904638631, 'e': 0.4082482904638631}d: {'b': 0.5, 'c': 0.5, 'e': 0.5, 'a': 0.4082482904638631}c: {'e': 0.5, 'd': 0.5, 'a': 0.4082482904638631}e: {'b': 0.5, 'c': 0.5, 'd': 0.5, 'a': 0.4082482904638631}
可以看到物品a和物品b的相关度是最高的。在得到物品的相关度之后,我们可以使用如下公式计算用户u对一个物品j的兴趣
N(u)是用户喜欢的物品集合,S(j, K)是物品j最相似的K个物品的集合,wji是物品j和i的相似度,rui是用户对物品i的兴趣,可令rui为1。我们可以把这个逻辑使用代码进行实现
1 |
def recommend(interacted_items: Union[set, dict], w, k): |
有了计算用户和物品相关度的代码,我们就可以把逻辑结合起来,实现向用户推荐物品了。完整的代码实现如下
1 |
import math |
以上代码的执行结果如下,可见用户B
和物品d
的相关度最高
b: {'a': 0.8164965809277261, 'd': 0.5, 'e': 0.5}d: {'b': 0.5, 'c': 0.5, 'e': 0.5, 'a': 0.4082482904638631}a: {'b': 0.8164965809277261, 'd': 0.4082482904638631, 'c': 0.4082482904638631, 'e': 0.4082482904638631}c: {'d': 0.5, 'e': 0.5, 'a': 0.4082482904638631}e: {'b': 0.5, 'c': 0.5, 'd': 0.5, 'a': 0.4082482904638631}d: 0.9082482904638631b: 0.8164965809277261e: 0.5
基于物品的推荐在工程中使用的比基于用户的推荐要多,因为UserCF(User Collaborative Filtering)的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF(Item Collaborative Filtering)的推荐更加个性化,反映了用户自己的兴趣传承。
隐语义模型核心思想是通过隐含特征(latent factor)联系用户兴趣和物品,它可以通过对数据进行分类来实现推荐。这种基于用户对数据的兴趣分类的方式,需要解决如下三个问题:
隐含语义分析技术(latent variable analysis)采取基于用户行为统计的自动聚类,来实现数据自动分类。
常用上下文:时间 & 地点
2025-06-19 01:44:52
Hooked: How to Build Habit-Forming Products
如何卖出更多的产品:产能 -> 营销/渠道 -> 产品设计
上瘾如何设计产品:触发 -> 行动 -> 多变的酬赏 -> 投入
习惯是大脑借以掌握复杂举动的途径之一。神经系统科学家指出,人脑中存在一个负责无意识行为的基底神经节,那些无意中产生的条件反射会以习惯的形式存储在基底神经节中,从而使人们腾出精力来关注其他的事物。当大脑试图走捷径而不再主动思考接下来该做些什么时,习惯就养成了。为解决当下面临的问题,大脑会在极短的时间内从行为存储库里提取出相宜的对策。
(就是基底核,有点像缓存的作用)
我们所要描述的体验更接近于“痒”,它是潜伏于我们内心的一种渴求,当这种渴求得不到满足时,不适感就会出现。那些让我们养成某种习惯的产品正好可以缓解这种不适感。比起听之任之的做法,利用技术或产品来”挠痒痒”能够更快地满足我们的渴求。一旦我们对某种技术或产品产生依赖,那它就是唯一的灵丹妙药了。
福格行为模型可以用公式来呈现,即B=MAT。B代表行为,M代表动机,A代表能力,T代表触发。要想使人们完成特定的行为,动机、能力、触发这三样缺一不可。1否则,人们将无法跨过”行动线”,也就是说,不会实施某种行为。
多变的酬赏主要表现为三种形式:社交酬赏,猎物酬赏,自我酬赏
沉没成本:通过用户对产品的投入程度,留住用户
总体评价:很薄的一本书,有部分的观点有参考意义,但是大部分的论调都是老生常谈。大多数的观点在很多心理学的书籍里面已经讲过了,本书主要是讲怎么依赖于这些原理来进行实操,有一定的参考意义。
2025-04-24 22:21:27
Generated By AI
类型 | Kotlin 写法 | Java 写法 | 简要说明 |
---|---|---|---|
数字 |
Int , Long , Float , Double , Short , Byte
|
int , long , float , double , short , byte
|
Kotlin 数值类型映射到相应的原生/包装类型。 |
布尔 | Boolean |
boolean |
只能取 true /false ,与数字不互通。 |
字符 | Char |
char |
单个 Unicode 字符,支持转义序列。 |
字符串 | String |
String |
不可变;支持多行文本块 """...""" 。 |
数组 |
Array<T> , IntArray 等 |
T[] |
提供原始类型专用数组如 IntArray 、ByteArray 。 |
无符号整型 |
UInt , ULong , UShort , UByte
|
无 | 编译时检查范围,运行时越界抛 IllegalArgumentException 。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
变量定义 |
int x = 10; final String name = "Tom";
|
var x = 10 val name = "Tom"
|
var 可变,val 只读;类型由编译器推断。 |
类 + 构造 | public class P { P(String n) { ... } } |
class P(val name: String) |
主构造中声明属性,自动生成字段 & 访问器。 |
数据类 | 手动写字段/构造/equals /toString
|
data class User(val id: Int, val n: String) |
data 自动生成常用方法 & 解构组件。 |
函数定义 | public int sum(int a, int b) { return a + b; } |
fun sum(a: Int, b: Int) = a + b |
表达式函数可省略大括号和 return 。 |
空安全 | if (s != null) len = s.length(); else len = 0; |
val len = s?.length ?: 0 |
String? 可空,?. 与 ?: 插入编译期空检查。 |
分支匹配 | switch(x) { case 1: ... } |
when(x) { 1 -> ...; else -> ... } |
when 是表达式,支持范围 & 任意对象比较。 |
循环 & 集合 | for(int i=0;i<10;i++)``list.stream().filter() |
for(i in 0 until 10)``list.filter{} |
0 until 生成 IntRange ;集合链式调用基于扩展函数。 |
单例 | class S { private static S i=new S(); … } |
object S { fun foo() {} } |
object 编译时生成线程安全单例,无需额外样板。 |
特性 | 示例 | 简要说明 |
---|---|---|
默认 & 命名参数 |
fun g(msg: String = "Hi", name: String = "You") g(name="Tom")
|
编译器生成默认方法,命名参数避免重载歧义。 |
扩展函数 | fun String.ex() = uppercase() |
编译后为静态方法,第一个参数是接收者,调用如成员方法。 |
解构声明 | val (x, y) = Point(1, 2) |
data class 自动生成 componentN() ,一行取多值。 |
密封类 | sealed class R; data class Ok(val d: String): R(); object Err: R() |
限定子类范围,when 可做穷尽检查。 |
内联函数 | inline fun <T> m(b: ()->T): T { … } |
在调用处展开函数体,减少高阶函数的运行时开销。 |
集合构造器 |
listOf(1, 2) , mutableListOf("A") , mapOf("a" to 1)
|
内建集合工厂函数,语法简洁;to 表示键值对。 |
数组构造器 |
arrayOf(1, 2) , intArrayOf(1, 2)
|
支持泛型与原始类型数组,避免装箱。 |
表达式返回值 |
val max = if (a > b) a else b val result = try { … } catch { … }
|
if 、when 、try 都是表达式,可直接赋值。 |
区间语法 & 步进 |
for (i in 1..5) , for (j in 1 until 5 step 2)
|
.. 表闭区间,until 表半开,step 控制步长。 |
字符串模板 |
"Hello, $name" "Length: ${s.length}"
|
$变量 可直接拼接,复杂表达式用 ${} 。 |
Lambda 尾随语法 | list.filter { it > 0 }.map { it * 2 } |
大括号可直接跟随函数调用,链式语法自然、简洁。 |
函数 | 用法示例 | 简要说明 |
---|---|---|
let |
user?.let { print(it.name) } |
非空时执行块,it 引用原对象。 |
apply |
User().apply { age = 18 } |
在对象上执行块并返回该对象,常用于初始化。 |
also |
list.also { println("init") } |
执行副作用并返回对象,常用于日志 / 调试。 |
run |
val r = run { compute(); result } |
无接收者的作用域块,返回最后一行结果。 |
with |
with(cfg) { load(); validate() } |
对象上下文块,this 指向接收者,返回结果。 |
takeIf |
str.takeIf { it.isNotBlank() } |
条件为真返回对象,否则返回 null 。 |
sequence |
sequenceOf(1,2,3).map { … } |
惰性集合处理,适合大规模数据管道。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
泛型 | List<String> |
List<String> |
支持协变 / 逆变(out / in )和 reified 泛型函数。 |
类型别名 | 无 | typealias Name = String |
简化复杂类型声明。 |
枚举类 | enum Color { RED, GREEN } |
enum class Color { RED, GREEN } |
支持在枚举中定义属性 & 方法。 |
内联类 | 无 | @JvmInline value class USD(val amount: Int) |
编译时包装或展开,零开销封装。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
类型检查 | if (obj instanceof String) |
if (obj is String) |
is 后自动智能转换,无需显式强转。 |
安全转换 | (String) obj |
obj as String / obj as? String
|
as? 安全转换失败返回 null 。 |
基本转换 | Integer.parseInt(str) |
str.toInt() , toDouble() , toLong()
|
通过扩展函数提供常见类型转换。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
条件 & 循环 |
if , switch , for , while , do-while
|
if , when , for , while , do-while
|
when 可做表达式,替代 switch 。 |
返回 & 跳转 |
return , break , continue , throw
|
同 Java | 支持在 lambda 中局部返回,如 return@label 。 |
异常处理 |
try-catch-finally , checked exception |
try-catch-finally ,无 checked exception |
Kotlin 不区分受检异常,简化错误处理。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
包声明 | package com.example; |
package com.example |
不需要分号。 |
导入 | import java.util.List; |
import java.util.List |
支持导入顶层函数和属性。 |
别名导入 | 无 | import foo.Bar as Baz |
解决命名冲突或简化引用。 |
功能 | Java 写法 | Kotlin 写法 | 简要说明 |
---|---|---|---|
接口默认实现 | default void f() {} |
接口中可直接写方法体 | 接口内方法可有实现,无需关键字。 |
抽象类 | abstract class Shape { … } |
abstract class Shape { … } |
抽象成员不需再加 abstract 前缀。 |
继承 & 覆写 | class A extends B { @Override … } |
class A : B() { override fun … } |
用 : 表示继承,override 必显式标注。 |
可见性修饰符 |
public /protected /private
|
public /protected /private /internal
|
internal 表示同模块内可见。 |
内部类 | class Outer { class Inner {} } |
class Outer { inner class Inner {} } |
默认是静态嵌套,加 inner 变为非静态内部类。 |
场景 | Java 写法(线程/异步) | Kotlin 写法(协程) | 简要说明 |
---|---|---|---|
启动任务 | new Thread(() -> work()).start(); |
GlobalScope.launch { work() } |
协程更轻量、省资源,适合大规模并发。 |
异步返回值 | Future<Integer> f = exec.submit(...); |
val result = async { compute() }.await() |
内建 async/await ,语义更清晰。 |
延迟执行 | Thread.sleep(1000) |
delay(1000) |
非阻塞挂起,不占用线程。 |
结构化并发 | 手动管理线程池和生命周期 | coroutineScope { … } |
协程作用域自动管理生命周期,避免泄漏。 |
功能 | Java 写法(Stream) | Kotlin 写法(扩展函数) | 简要说明 |
---|---|---|---|
过滤 | list.stream().filter(x -> x > 0).collect(...) |
list.filter { it > 0 } |
语法简洁,链式调用更直观。 |
映射 | list.stream().map(x -> x * 2).collect(...) |
list.map { it * 2 } |
Lambda 简洁,扩展函数无额外依赖。 |
分组 | Collectors.groupingBy(...) |
list.groupBy { it.key } |
直接返回 Map<K, List<V>> ,更易读。 |
排序 | list.sort(Comparator.comparing(...)) |
list.sortedBy { it.prop } |
函数式排序,链式可读性好。 |
聚合 |
reduce , sum , collect
|
reduce , sumOf , fold
|
内建多种聚合函数,常用时无需额外导入。 |
持续更新中…
2025-03-31 19:21:10
简单起见,又或者是没有N卡或者显卡的配置比较低,可以使用腾讯云HAI直接搭建ComfyUI服务。
可以先clear掉当前的workflow,之后右键新建模块。
首先需要新建一个采样器(KSampler),采样器的配置如下:
参数 | 值 | 说明 |
---|---|---|
seed | 0 | 随机种子,控制生成结果的随机性。相同种子会产生相同结果 |
control_after_generate | randomize | 生成后种子控制方式:randomize(随机化)、increment(递增)、decrement(递减)、fixed(固定) |
steps | 20 | 采样步数,通常15-30步较为合适。步数越多质量越好但耗时更长 |
cfg | 8.000 | CFG引导强度,控制AI对提示词的遵循程度。范围1-20,推荐7-12 |
sampler_name | euler | 采样算法:euler、euler_a、dpm_2、dpm_2_ancestral、lms、ddim等 |
scheduler | normal | 调度器类型:normal、karras、exponential、sgm_uniform等 |
denoise | 1.000 | 去噪强度,1.0为完全去噪,0.0为不去噪。图生图时可调节此值 |
采样器的model可以选择Load Checkpoint。
positive是正向提示词,negative是反向提示词,都可以选择CLIPTextEncode。为了方便区分,可以给prompt修改一个有意义的标题。
latent_image可以设置图片的选项,例如EmptyLatentImage。
LATENT可以选择VAE Decode模块,然后可以添加一个图片预览模块。
输出大小设置为图片背景总大小。
图片正向prompt输出拖拽,然后添加Conditioning(Set Area)节点,在其中设置图片的大小和图片在背景画布中的位置。
使用ConditioningCombine节点,合并多个Conditioning节点,之后把合并节点的输出连接到KSample。
但是,如果是单纯两个prompt直接连到采样器,两张图片会很割裂。解决办法是再新建一个prompt,之后把两张图片的输出和这个prompt使用一个combine进行合并,之后合并结果再输出到采样器。
CheckPoints(检查点模型)是Stable Diffusion的核心基础模型,包含了完整的图像生成能力。它是一个预训练的神经网络模型,决定了生成图像的整体风格、质量和特征。
模型类型 | 特点 | 适用场景 | 推荐模型 |
---|---|---|---|
SD 1.5系列 | 经典基础模型,兼容性好 | 入门学习,插件丰富 | v1-5-pruned-emaonly.ckpt |
SDXL系列 | 更高分辨率,质量更好 | 高质量出图 | sd_xl_base_1.0.safetensors |
写实人像 | 专注真实人物生成 | 人像摄影、写实风格 | realisticVisionV60B1_v51VAE.safetensors |
动漫二次元 | 卡通动漫风格 | 动漫插画、角色设计 | anything-v5-PrtRE.safetensors |
艺术绘画 | 艺术风格强烈 | 创意艺术、概念设计 | dreamshaper_8.safetensors |
建筑风景 | 专注场景和建筑 | 建筑设计、风景画 | architectureExterior_v40.safetensors |
模型文件格式:.ckpt
:早期格式,文件较大;.safetensors
:更安全的格式,加载速度快,推荐使用;.pt
:PyTorch原生格式
使用LoRA模型:在主模型的MODEL节点拖拽,可以新增LoRA节点,之后把LoRA的模型替代主模型连接到采样器上面。同样的,主模型的CLIP需要连接到LoRA上面,之后把正向prompt连接到LoRA就可以了(反向prompt还是连接在CheckPoint的CLIP上面)。
LoRA和Checkpoint的定义区别如下
名称 | 定义 |
---|---|
Checkpoint | 模型的权重文件,是模型在某个训练时刻的完整状态的保存,通常包括整个模型的参数、优化器状态等。 |
LoRA(Low-Rank Adaptation) | 一种参数高效的微调方法,不直接修改原始模型参数,而是在模型某些层中引入少量可训练参数,从而在不改变大模型主体的前提下实现微调。 |
它们有着各自不同的使用目的
比较项 | Checkpoint | LoRA |
---|---|---|
目标 | 保存和恢复训练过程或完整模型 | 节省参数、内存,快速高效地微调大模型 |
应用场景 | 模型训练中断恢复、部署模型 | 微调大语言模型、个性化调整、插件式部署 |
文件大小 | 通常非常大(几百MB到几十GB) | 非常小(几MB到几十MB) |
首先我们打开腾讯云的HAI界面,并新建一个ComfyUI的服务
服务创建好了之后我们打开对应的ComfyUI服务地址,可以看到如下界面
这里我们禁用掉新版UI,并且安装Crystools节点
之后我们可以看到界面会变成如下样式
随后我们下载并安装realisticVision模型
创建好服务的密码等信息会通过站内信的方式进行发送,可以使用这些信息登录到机器上,之后在机器上面下载realisticVision模型
在ComfyUI/models/checkpoints/
文件夹里面下载该模型
1 |
(base) root@VM-0-5-ubuntu:~# cd ComfyUI/models/checkpoints/ |
之后点击Refresh按钮,可以在checkpoint节点里面看到我们刚刚下载的模型
我们使用这个模型根据一些prompt可以生成一张福特福克斯的图片
刚刚我们的模型是在huggingface上面下载的,civitai同样也可以下载模型
使用命令下载civitai的模型,这里需要注册账号生成token并把token填入到下载链接中
1 |
(base) root@VM-0-5-ubuntu:~/ComfyUI/models/checkpoints# wget --content-disposition 'https://civitai.com/api/download/models/501240?type=Model&format=SafeTensor&size=pruned&fp=fp16&token=${API_TOKEN}' |
刷新之后同样可以看到新的模型信息
之后我们使用civitai上面的推荐配置
使用推荐配置即可生成相似的图片
实际生成的图片如下,ComfyUI生成的图片会包含生成图片时用到的整个工作流,如果需要使用相同的工作流只需要在ComfyUI中导入该图片即可
如上图片的信息如下
prompt
1 |
{"4": {"inputs": {"ckpt_name": "realisticVisionV60B1_v51HyperVAE.safetensors"}, "class_type": "CheckpointLoaderSimple", "_meta": {"title": "Load Checkpoint"}}, "5": {"inputs": {"width": 512, "height": 512, "batch_size": 1}, "class_type": "EmptyLatentImage", "_meta": {"title": "Empty Latent Image"}}, "6": {"inputs": {"text": "instagram photo, front shot, portrait photo of a 24 y.o woman, wearing dress, beautiful face, cinematic shot, dark shot", "clip": ["4", 1]}, "class_type": "CLIPTextEncode", "_meta": {"title": "CLIP Text Encode (Prompt)"}}, "7": {"inputs": {"text": "(nsfw, naked, nude, deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, mutated hands and fingers:1.4), (deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, disconnected limbs, mutation, mutated, ugly, disgusting, amputation", "clip": ["4", 1]}, "class_type": "CLIPTextEncode", "_meta": {"title": "CLIP Text Encode (Prompt)"}}, "8": {"inputs": {"samples": ["10", 0], "vae": ["4", 2]}, "class_type": "VAEDecode", "_meta": {"title": "VAE Decode"}}, "9": {"inputs": {"filename_prefix": "ComfyUI", "images": ["8", 0]}, "class_type": "SaveImage", "_meta": {"title": "Save Image"}}, "10": {"inputs": {"add_noise": "enable", "noise_seed": 794547607621135, "steps": 6, "cfg": 1.5, "sampler_name": "dpmpp_sde", "scheduler": "normal", "start_at_step": 0, "end_at_step": 10000, "return_with_leftover_noise": "disable", "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0]}, "class_type": "KSamplerAdvanced", "_meta": {"title": "KSampler (Advanced)"}}} |
workflow
1 |
{"last_node_id": 10, "last_link_id": 16, "nodes": [{"id": 9, "type": "SaveImage", "pos": [1451, 189], "size": [210, 270], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "images", "type": "IMAGE", "link": 9, "label": "images"}], "outputs": [], "properties": {}, "widgets_values": ["ComfyUI"]}, {"id": 4, "type": "CheckpointLoaderSimple", "pos": [26, 474], "size": [315, 98], "flags": {}, "order": 0, "mode": 0, "inputs": [], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [10], "slot_index": 0, "label": "MODEL"}, {"name": "CLIP", "type": "CLIP", "links": [3, 5], "slot_index": 1, "label": "CLIP"}, {"name": "VAE", "type": "VAE", "links": [8], "slot_index": 2, "label": "VAE"}], "properties": {"Node name for S&R": "CheckpointLoaderSimple"}, "widgets_values": ["realisticVisionV60B1_v51HyperVAE.safetensors"]}, {"id": 7, "type": "CLIPTextEncode", "pos": [413, 389], "size": [425.27801513671875, 180.6060791015625], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 5, "label": "clip"}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [12], "slot_index": 0, "label": "CONDITIONING"}], "properties": {"Node name for S&R": "CLIPTextEncode"}, "widgets_values": ["(nsfw, naked, nude, deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, mutated hands and fingers:1.4), (deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, disconnected limbs, mutation, mutated, ugly, disgusting, amputation"]}, {"id": 8, "type": "VAEDecode", "pos": [1209, 188], "size": [210, 46], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 16, "label": "samples"}, {"name": "vae", "type": "VAE", "link": 8, "label": "vae"}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0, "label": "IMAGE"}], "properties": {"Node name for S&R": "VAEDecode"}, "widgets_values": []}, {"id": 10, "type": "KSamplerAdvanced", "pos": [862.98046875, 167.984375], "size": [315, 334], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 10, "label": "model"}, {"name": "positive", "type": "CONDITIONING", "link": 11, "label": "positive"}, {"name": "negative", "type": "CONDITIONING", "link": 12, "label": "negative"}, {"name": "latent_image", "type": "LATENT", "link": 15, "label": "latent_image"}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [16], "label": "LATENT"}], "properties": {"Node name for S&R": "KSamplerAdvanced"}, "widgets_values": ["enable", 794547607621135, "randomize", 6, 1.5, "dpmpp_sde", "normal", 0, 10000, "disable"]}, {"id": 6, "type": "CLIPTextEncode", "pos": [415, 186], "size": [422.84503173828125, 164.31304931640625], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 3, "label": "clip"}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [11], "slot_index": 0, "label": "CONDITIONING"}], "properties": {"Node name for S&R": "CLIPTextEncode"}, "widgets_values": ["instagram photo, front shot, portrait photo of a 24 y.o woman, wearing dress, beautiful face, cinematic shot, dark shot"]}, {"id": 5, "type": "EmptyLatentImage", "pos": [473, 609], "size": [315, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [15], "slot_index": 0, "label": "LATENT"}], "properties": {"Node name for S&R": "EmptyLatentImage"}, "widgets_values": [512, 512, 1]}], "links": [[3, 4, 1, 6, 0, "CLIP"], [5, 4, 1, 7, 0, "CLIP"], [8, 4, 2, 8, 1, "VAE"], [9, 8, 0, 9, 0, "IMAGE"], [10, 4, 0, 10, 0, "MODEL"], [11, 6, 0, 10, 1, "CONDITIONING"], [12, 7, 0, 10, 2, "CONDITIONING"], [15, 5, 0, 10, 3, "LATENT"], [16, 10, 0, 8, 0, "LATENT"]], "groups": [], "config": {}, "extra": {"ds": {"scale": 1, "offset": [2, 0]}, "node_versions": {"comfy-core": "0.3.14"}}, "version": 0.4} |
首先我们下载sd_xl_base_1.0模型,LoRA需要使用与之对应的基础模型
1 |
cd ComfyUI/models/checkpoints/ |
之后我们再下载LoRA模型,这里我们使用的是Expressionism Cartoons模型
1 |
(base) root@VM-0-5-ubuntu:~# cd ComfyUI/models/loras/ |
之后我们根据LoRA模型在Civitai提供的推荐设置,就可以生成对应的图片了
生成的图片如下
首先需要安装comfyui_controlnet_aux
节点,之后还要下载模型。ContrlNet模型是分版本的,与基础大模型要对应。如果基础模型是SD1.5,ControlNet模型也要选择SD1.5。我们从地址 https://huggingface.co/lllyasviel/ControlNet-v1-1/tree/main 可以下载模型,模型的不同类型含义如下
文件名 | 控制类型 | 输入图像类型 | 用途说明 | 推荐用途 |
---|---|---|---|---|
control_v11p_sd15_canny.pth |
Canny Edge(边缘) | 照片或图像 | 提取轮廓边缘 | 建筑、物体结构控制 |
control_v11p_sd15_depth.pth |
Depth(深度) | 照片或图像 | 模拟三维深度结构 | 景深、构图调整 |
control_v11p_sd15_inpaint.pth |
Inpainting(重绘) | 遮挡/蒙版图像 | 区域重绘 | 修图、局部创作 |
control_v11p_sd15_lineart.pth |
Line Art(线稿) | 动漫风图像 | 提取黑白线稿 | 动漫风格建模 |
control_v11p_sd15_mlsd.pth |
MLSD(直线检测) | 建筑/结构图像 | 提取直线 | 建筑、平面图等直线构图 |
control_v11p_sd15_normalbae.pth |
Normal Map(法线图) | 三维物体图像 | 表面结构建模 | 高级3D拟合 |
control_v11p_sd15_openpose.pth |
OpenPose(姿态) | 人物图像 | 骨架动作识别 | 指定人物动作/舞姿 |
control_v11p_sd15_scribble.pth |
Scribble(手绘草图) | 简笔画、草图 | 草图转图像 | 草图创作、简笔画上色 |
control_v11p_sd15_seg.pth |
Semantic Segmentation(分割图) | 分割图(每块颜色代表一种物体) | 提供语义结构控制 | 多物体控制,如人物+背景 |
control_v11p_sd15_softedge.pth |
Soft Edge(柔边缘) | 模糊边缘图 | 类似 Canny,但更柔和 | 人像、柔边轮廓控制 |
control_v11p_sd15s2_lineart_anime.pth |
Anime Line Art | 动漫线稿 | 动漫草图 → 动漫图 | 高拟真二次元创作 |
我们这里只需要下载我们所需要的模型即可
1 |
cd ComfyUI/models/controlnet/ |
下载好了模型之后我们先新建一个默认工作流。之后创建如下节点:
加载原始输入图像,作为ControlNet的参考图像
Add Node -> image -> Load Image
使用Canny边缘检测算法提取输入图像的边缘轮廓,生成黑白边缘图
Add Node -> ControlNet Preprocessors -> Line Extractors -> Canny Edge
加载ControlNet预训练模型文件,如control_v11p_sd15_canny.pth等
Add Node -> loaders -> Load ControlNet Model
将ControlNet模型应用到条件编码上,结合边缘图控制生成过程
Add Node -> conditioning -> controlnet -> Apply ControlNet
预览最终生成的图像结果
Add Node -> image -> Preview Image
建好了工作节点之后,我们可以使用Canny Edge为图片生成线图
之后我们对工作流进行整合,从而根据原图生成类似的图片
实际生成的图片如下
SDXL提供了多合一的ControlNet模型,不再需要下载多个文件,只需要使用一个模型即可,能有效节省磁盘空间。
cd ComfyUI/models/controlnet/wget 'https://huggingface.co/xinsir/controlnet-union-sdxl-1.0/resolve/main/diffusion_pytorch_model_promax.safetensors'
下载好了之后,我们还是先加载默认工作流,之后创建ControlNet的相关节点。在创建的时候可以使用鼠标左键快速双击,然后在输入框中搜索需要使用的节点。
因为这里需要使用SDXL的模型,所以我们还是使用sd_xl_base_1.0
模型,并且搭配LoRA一起使用
2025-03-31 06:47:59
ComfyUI 是一个基于节点工作流的现代化 Stable Diffusion 图形用户界面。与传统的WebUI不同,ComfyUI采用节点连接的方式来构建图像生成工作流,让用户能够更精确地控制整个生成过程。
Stable Diffusion 是一款开源的 AI 图像生成技术,基于扩散模型构建。用户可以通过 Stable Diffusion WebUI 或 ComfyUI 等开源工具来运行它,只需下载相应的模型文件(通常为 .ckpt
或 .safetensors
格式)即可开始使用。
ComfyUI中的图像生成涉及三个关键组件,在CheckpointLoader中进行设置:
从 GitHub 下载对应版本,解压后运行:
run_nvidia_gpu.bat
(推荐NVIDIA GPU用户)./python_embeded/python -s ComfyUI/main.py --windows-standalone-build
下载 ComfyUI-Manager 放到 ComfyUI/custom_nodes
文件夹,然后在Manager的Custom Node Manager中安装所需插件(需要科学上网)。
将模型文件放置到 ComfyUI/models
文件夹中:
checkpoints
文件夹loras
文件夹 vae
文件夹推荐入门模型:SD 1.5
模型资源网站:
如果遇到网络连接问题,可以使用SwitchHosts添加以下配置:
1 |
185.199.108.133 raw.githubusercontent.com |
通过Custom Node Manager安装,然后执行以下命令安装依赖:
1 |
./python_embeded/python.exe -m pip install -r ../ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI_StoryDiffusion/requirements.txt |
1 |
curl -X POST 'http://127.0.0.1:8188/prompt' \ |
curl -X GET 'http://127.0.0.1:8188/history/{prompt_id}'
http://127.0.0.1:8188/view?filename=ComfyUI_00003_.png&subfolder&type=output
更简单的实时通信方式:
1 |
// 建立连接 |
提示:如果不想折腾本地环境,可以考虑使用腾讯云等平台提供的按时计费ComfyUI服务。
官方资源
学习教程
2025-01-02 18:37:24
whisper是一个由openai开发的通用语言识别模型,我们可以使用它来为视频自动创建字幕。
为了加速,我们需要使用GPU来进行计算,因此需要安装基于CUDA的pytorch。首先我们需要安装Miniconda,这里安装的时候直接点击下一步即可。
安装完毕之后,我们需要创建一个新的环境,这里我们创建一个名为whisper
的环境:
conda create -n whisper python=3.8conda activate whisper
安装好了Miniconda之后,我们需要安装CUDA,执行nvidia-smi
$ nvidia-smiThu Jan 2 11:49:53 2025+-----------------------------------------------------------------------------------------+| NVIDIA-SMI 560.94 Driver Version: 560.94 CUDA Version: 12.6 ||-----------------------------------------+------------------------+----------------------+| GPU Name Driver-Model | Bus-Id Disp.A | Volatile Uncorr. ECC || Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. || | | MIG M. ||=========================================+========================+======================|| 0 NVIDIA GeForce GTX 1060 6GB WDDM | 00000000:01:00.0 On | N/A || 0% 39C P8 10W / 120W | 505MiB / 6144MiB | 0% Default || | | N/A |+-----------------------------------------+------------------------+----------------------+
通过这个命令可以看到Driver Version: 560.94
和CUDA Version: 12.6
,因此我们需要安装12.6版本的CUDA,更加详细的版本对照表在这里。在安装的时候可以选择自定义安装选项,一般来说只要勾选CUDA下的 Development和Runtime即可。
安装完毕之后执行命令nvcc -V
查看CUDA版本:
$ nvcc -Vnvcc: NVIDIA (R) Cuda compiler driverCopyright (c) 2005-2024 NVIDIA CorporationBuilt on Thu_Sep_12_02:55:00_Pacific_Daylight_Time_2024Cuda compilation tools, release 12.6, V12.6.77Build cuda_12.6.r12.6/compiler.34841621_0
根据自己下载的CUDA来选择对应版本的cuDNN,下载地址在这里。下载完毕之后解压到CUDA的安装目录下,一般来说是C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA{版本号}
,如果有重名的文件直接替换即可。
之后进入extras\demo_suite
目录,执行如下命令:
bandwidthTest.exedeviceQuery.exe
如果出现了PASS的字样,说明安装成功。
切换到我们之前创建的whisper
环境,使用如下命令安装CUDA版本的pytorch:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
安装之后执行python命令进入python环境,执行如下代码:
1 |
import torch |
如果显示True则说明CUDA版本的pytorch安装成功。
切换到我们之前创建的whisper
环境,执行如下命令安装whisper:
pip install -U openai-whisperpip install setuptools-rust
安装完毕之后执行如下命令就可以使用whisper了:
whisper 'C:/Users/raymond/Desktop/voice.aac' --language zh --model turbo
如上命令表示对C:/Users/raymond/Desktop/voice.aac
文件进行中文语言的识别,使用turbo模型。第一次执行该命令会下载模型文件,模型文件较大,下载时请确保网络通畅。执行结果如下
[00:00.000 --> 00:03.060] 提到肉毒毒素[00:03.060 --> 00:04.540] 你会想到什么[00:04.540 --> 00:10.820] 你真的了解它吗[00:10.820 --> 00:12.540] 2017年[00:12.540 --> 00:14.180] 肉毒毒素以万能药标签[00:14.180 --> 00:15.500] 登上时代周刊方面[00:15.500 --> 00:17.280] 目前它在全球[00:17.280 --> 00:18.960] 已被应用于几十种适应症[00:18.960 --> 00:20.560] 仅在2019年[00:20.560 --> 00:23.000] 接受注射的就已超过620万例[00:23.000 --> 00:24.880] 但不要忘了[00:24.880 --> 00:26.780] 肉毒毒素更是一种神经毒素[00:26.780 --> 00:29.000] 还曾被当作生化武器使用... 省略 ...
我们可以使用ffmpeg将音频从视频中提取出来,然后使用whisper生成字幕,最后使用ffmpeg将字幕添加到视频中。
使用如下命令提取音频:
ffmpeg -i input.mp4 -vn -acodec copy output.aac
然后使用whisper生成字幕,我们先在pycharm中创建一个test-whisper
项目,并且把python解释器设置为Miniconda创建的whisper
环境。创建一个main.py
文件,写入如下代码:
1 |
import whisper |
如上代码表示使用turbo模型,识别中文,打印详细信息,并且保存字幕文件。执行完毕之后我们可以在E:/
目录下看到生成的字幕文件。
最后我们使用ffmpeg将字幕添加到视频中:
ffmpeg -i input.mp4 -i output.srt -c:s mov_text -c:v copy -c:a copy output.mp4
之后我们在播放这个视频的时候就会有字幕了。
video-subtitle-generator
基于Anaconda的pytorch-cuda
CUDA与cuDNN的安装与配置
ffmpeg视频合并、格式转换、截图