如何用梯度下降优化余弦相似度?0-1权重向量组合求解
问题分析与解决方案
原代码核心问题
- 禁用Eager Execution:TensorFlow 1.x图模式下,所有操作都是张量节点,直接打印只会输出结构,无法获取实际数值,这是看不到变量具体值的根本原因。
- 变量管理混乱:每次循环重新创建
tf.Variable,导致梯度无法在迭代中持续传递,训练完全无效。 - 离散操作不可导:
tf.round是离散操作,梯度为0,梯度下降无法更新变量,必须用连续松弛方法。 - 低效的手动循环:手动遍历维度计算B,既慢又容易出错,应该用矩阵运算替代。
- 除以0风险:加权平均时若所有权重为0,
sum(variables)为0会触发错误。
可行实现思路
这是一个组合优化问题(选择C的子集,加权平均后与A的余弦相似度最大),直接用梯度下降处理离散0/1变量不可行,需采用连续松弛策略:
- 将权重定义为可训练的连续变量,用sigmoid函数约束在[0,1]区间。
- 基于连续权重优化损失(1 - 余弦相似度),最大化A与B的相似度等价于最小化该损失。
- 训练完成后,将连续权重四舍五入为0/1,得到离散解。
- 用Eager Execution(TensorFlow 2.x默认开启),方便实时查看变量数值和训练过程。
修正后的代码
import tensorflow as tf import numpy as np # 向量维度 d = 1508 # 目标向量A(转为TensorFlow张量) A = tf.convert_to_tensor(np.random.uniform(0, 1, size=d), dtype=tf.float32) # 向量列表C(转为形状为[n, d]的张量) n = 4 C = tf.convert_to_tensor(np.random.uniform(0, 1, size=(n, d)), dtype=tf.float32) # 定义可训练的连续权重变量,初始值设为0.5 weights = tf.Variable(tf.ones(n, dtype=tf.float32) * 0.5, trainable=True) # 优化器选择Adam(比SGD更适合这类问题) optimizer = tf.keras.optimizers.Adam(learning_rate=0.1) num_steps = 500 for step in range(num_steps): with tf.GradientTape() as tape: # 用sigmoid将权重约束在[0,1]区间 continuous_weights = tf.sigmoid(weights) # 计算加权和:(n,) @ (n,d) -> (d,) weighted_sum = tf.tensordot(continuous_weights, C, axes=1) # 计算权重和,加极小值避免除以0 sum_weights = tf.reduce_sum(continuous_weights) + 1e-8 # 得到加权平均向量B B = weighted_sum / sum_weights # 计算损失:1 - 余弦相似度(最小化损失等价于最大化相似度) loss = 1 - tf.keras.losses.cosine_similarity(A, B) # 计算梯度并更新权重 grads = tape.gradient(loss, weights) optimizer.apply_gradients([(grads, weights)]) # 每50步打印一次训练状态 if step % 50 == 0: print(f"Step {step}, Loss: {loss.numpy():.4f}") print(f"Current continuous weights: {continuous_weights.numpy()}") # 训练完成后,将连续权重四舍五入为0/1 discrete_weights = tf.round(continuous_weights).numpy() print("\nFinal discrete weights (0/1):", discrete_weights) # 验证最终B与A的余弦相似度 final_weighted_sum = tf.tensordot(discrete_weights, C, axes=1) final_sum_weights = tf.reduce_sum(discrete_weights) + 1e-8 final_B = final_weighted_sum / final_sum_weights final_similarity = 1 - loss(A, final_B).numpy() print(f"Final cosine similarity between A and B: {final_similarity:.4f}")
关键说明
- Eager Execution:默认开启,所有操作实时计算,通过
.numpy()方法可以直接获取张量的数值。 - 连续松弛:用
sigmoid(weights)替代直接0/1变量,保证梯度可导,训练后再通过tf.round得到离散解。 - 矩阵运算:用
tf.tensordot替代手动循环,大幅提升计算效率,尤其适合高维度向量。 - 边界处理:加
1e-8避免权重和为0时的除以0错误。 - 变量管理:只定义一次
tf.Variable,迭代中持续更新,保证梯度传递有效。
额外提示
如果n较小(比如n<20),也可以用暴力枚举法遍历所有2^n种可能的权重组合,直接计算每种组合下的余弦相似度,选出最优解,这种方法结果绝对最优,不需要梯度下降。但n较大时,必须用启发式方法(如连续松弛+梯度下降)近似求解。
内容的提问来源于stack exchange,提问作者Constantly Groovin'
相关产品推荐
相关产品推荐

