一直都再说AI存在幻觉。不过随着大语言模型的技术进步,用户提问技巧的不断完善,对于大多数用户提出的一般性问题,AI结果的幻觉可能性确实在下降。

但是这并不意味着AI没有幻觉,相反,可能幻觉现象会表现的更为隐蔽。

下面结合一个具体的编程实践来说明下这个问题。

起因是希望利用大语言模型(DeepSeek)来完成用户评分行为的一致性检测问题。我先把这个问题简单说明下。所谓一致性是指用户给观看的电影打分时,会因为看到已有的电影历史平均评分,而可能调整自己原先的评分,使自己的打分更趋近于电影历史平均评分。比如用户A本想对看到的电影打2分,但是看到现在该电影的历史平均分为4.5分,于是最终还是打出了3分。这多出的1分都是一致性的表现。

利用AI可以生成检查用户打分与每部电影历史平均分的差值的代码,并可以统计这个差值平均值。本例不打算对此多做说明,仅列出AI生成并加以人工调整后的代码:

import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
ratings.sort_values(by='timestamp', inplace=True)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = ratings['rating'] - ratings['avg_rating']
print(ratings[['diff']].mean())

运行结果为:diff   -0.025721

这里有一个很大的问题,那就是如何证明这个差值比较小,只有比正常的小,才能说明用户打分更接近每部电影历史平均分。

这里采取了一种简单的方案,那就是打乱所有行记录,使用同样的方法再次计算差值,因为打乱了,此时的数据应该体现不了用户打分更接近每部电影历史平均分,因此差值会变大,再次使用AI写出了代码:

import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
#打乱ratings次序
ratings = ratings.sample(frac=1)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = ratings['rating'] - ratings['avg_rating']
print(ratings[['diff']].mean())

运行结果为:diff   -0.001163

现在问题来了,为什么打乱后反而变小了呢?应该变大才是!就这个问题,下面开始了一段很有意思的AI提问之旅。

第一次的提问很简单:

下面是两段检测用户评分是否存在一致性的代码,思路是利用每个用户对当前电影评分和该电影历史平均评分得到差值,最终计算所有平均值,第一段代码按照正常时间次序,差值为-0.025721,第二段代码打乱时序,差值为-0.013539,这是否可以说明存在一致性(Conformity)

import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
ratings.sort_values(by='timestamp', inplace=True)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = ratings['rating'] - ratings['avg_rating']
print(ratings[['diff']].mean())

diff   -0.025721

import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
#打乱ratings次序
ratings = ratings.sample(frac=1)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = ratings['rating'] - ratings['avg_rating']
print(ratings[['diff']].mean())

diff   -0.013539

对于这个奇怪的结果,大语言模型给出了很多解释,它的基本要点有几个:

一是负值本身就说明存在一致性,如果用户完全独立评分,那么用户评分与历史平均分的差值在统计上应该围绕 0 上下随机波动,均值趋近于 0。在两次结果中(-0.0257 和 -0.0135),均值均为负数。这意味着,平均而言,用户对电影的最新评分,低于该电影之前已有的平均分。为什么是负值而不是正值,大语言模型也给出了解释,它说高评分电影在早期容易被发现并打分,随着时间推移和受众扩大,后期评分会向均值回归(或略有下降)。只要均值显著不为 0,就说明用户的评分行为受到了“历史分数”这个锚点的影响(即一致性)。

二是为什么两次结果不一样,大语言模型认为数据被打乱后,随机顺序下的历史平均相当于破坏了时间因果关系,强行构建了一个“虚拟历史”。在这种情况下,计算出的差值更接近于纯随机误差,因此负值缩小到了 -0.0135。而最初的时序数据则包含了“时间趋势”带来的噪声(如早期粉丝效应,打乱后的数据消除了时间趋势,更接近于纯粹由“一致性(锚定效应)”带来的偏差。


对于这个分析结论,可以不断的再次提问,让我们觉得非常有道理。但是我总觉得哪里有点问题。虽然大语言模型目前给出的一些建议和思考策略,确实也具有启发性,但是事实上,这个Movielens数据集的评分时间区间很窄,只有不到2年的时间,不太可能出现早期打分更高的现象。我反复向大语言模型确定这个问题,大语言模型给出了看起来越来越让人信服的结论:

以下内容来自于大语言模型的补充回答:

你的追问非常到位!这确实是整个逻辑链条中最容易让人困惑的地方。我之前的表述确实有跳跃,现在我把中间缺失的逻辑环节补上,让你彻底理解。

为什么「绝对值更大」意味着「更靠近历史平均分」?

1. 先看本质:diff的正负号说明了什么?

text

diff = 当前评分 - 历史平均分
  • diff > 0:用户评分 高于 历史平均分

  • diff < 0:用户评分 低于 历史平均分

  • diff = 0:用户评分 等于 历史平均分


2. 一致性效应是如何起作用的?

当存在一致性时,用户的评分会被「拽」向历史平均分:

场景 用户真实想法 历史平均分 受一致性影响后的评分 diff
用户喜欢高分电影 想打 5.0 4.5 被拽低 → 打 4.7 +0.2
用户不喜欢高分电影 想打 3.0 4.5 被拽高 → 打 3.8 -0.7

关键点:一致性让 diff 无限趋近于 0,但永远不会等于 0(因为如果是 0,说明用户完全随大流,失去个人判断)。


3. 为什么「绝对值更大」反而意味着「更靠近」?

这里你需要理解一个统计学上的反直觉现象

在有时间顺序的真实数据中

  • 早期评分者大多是粉丝 → 评分极高 → 历史平均分被拉高

  • 后期普通用户涌入 → 受一致性影响,评分被「拽」高

  • 但因为普通用户本质上没那么狂热,再怎么被拽,也追不上早期粉丝打出的高分

  • 结果:大量后期的 diff 都是负的,且负得「比较厉害」

在打乱时序的虚假数据中

  • 历史平均分由随机抽取的样本计算,不再有「早期粉丝拉高均值」的现象

  • 用户评分也随机散布,不受真实历史的影响

  • 结果diff 的正负更加均衡,相互抵消,总体均值更接近 0


4. 用一个具体例子彻底搞清楚

假设电影《泰坦尼克号》:

版本 时间顺序 评分者 评分 历史平均分(前序) diff
时序 第1个 影迷A 5.0 无(跳过) -
时序 第2个 影迷B 4.8 5.0 -0.2
时序 第100个 普通观众C 4.2 4.6 -0.4
时序 第1000个 普通观众D 3.8 4.3 -0.5
打乱 随机 普通观众E 4.0 4.0(随机抽的样本) 0.0
打乱 随机 普通观众F 3.5 4.2(随机抽的样本) -0.7
打乱 随机 普通观众G 4.5 3.8(随机抽的样本) +0.7

观察

  • 时序版本:所有 diff 都是负的,且 绝对值较大(-0.2, -0.4, -0.5),均值 = -0.367

  • 打乱版本diff 有正有负,相互抵消,均值 = 0.0

结论

时序版本中,每个用户都在「努力向历史平均分靠拢」,但因为历史平均分被早期粉丝拉得太高,他们永远追不上,所以 diff 总是负的,且负得「很稳定」。这种稳定的大绝对值,恰恰证明了一致性在起作用。


5. 用数学重新表述(消除歧义)

你应该这样理解:

指标 含义
diff 的绝对值 用户评分与历史平均分的距离
diff 的稳定性 所有用户是否同向偏离(全是正或全是负)

一致性存在的标志

  1. diff 的方向高度一致(比如全是负的)

  2. diff 的绝对值较大(因为历史均值被早期极端值拉偏了)

一致性被破坏的标志

  1. diff 的方向随机(正负各半)

  2. diff 的绝对值较小(正负抵消,总体均值接近0)


6. 你代码中两个数据的真实含义

版本 diff均值 绝对值 解读
时序 -0.0257 0.0257 所有用户的 diff 同向为负(一致性在起作用),均值偏离0较远
打乱 -0.0135 0.0135 正负 diff 相互抵消(一致性被破坏),均值更接近0

所以:

时序版本的绝对值更大(0.0257 > 0.0135),因为它保留了时间顺序,用户看到了历史评分,产生了“向历史均值靠拢”的行为,导致所有用户的 diff 都稳定地偏向负方向。


7. 用物理学类比帮助理解

想象你在射箭:

  • 时序版本:所有箭都射在了靶心偏左的位置,虽然不在中心,但聚集在一起(说明有外力在拉扯它们)

  • 打乱版本:箭射得散布在靶心周围,正负抵消,平均值反而接近中心(说明没有外力拉扯)

在这个类比中:

  • 靶心 = 0(完全独立评分)

  • 偏左 = diff 为负(一致性在起作用)

  • 聚集 = 所有用户行为一致(一致性在起作用)


一句话终极总结

diff 的绝对值不是「靠近程度」的直接度量,而是「一致性强度」的间接指标。

正是因为一致性把所有人的 diff 都拽向了同一个方向(负),导致均值偏离 0 更远(绝对值更大),而不是更近。

现在你明白为什么「绝对值更大」意味着「更靠近历史平均分」了吗?核心在于:一致性让所有人同向偏离,而不是各自随机波动。

看的出来,大语言模型从各个角度来试图证明它的观点,但是关键的问题仍然在于我知道这个数据并不可能存在早期评分更高的现象,我没有完全盲从大语言模型的结论,仔细检查了代码,最后发现一个错误:

ratings['diff'] = ratings['rating'] - ratings['avg_rating']

这段代码是错误的,因为没有取绝对值,因此有正有负,计算下面的平均值时,甚至会相互抵消,从而造成结果错误,比如两个用户差值一个为+0.5,另一个为-0.5,相加平均后反而为0,其实差值很大!

正确的写法应该是加上abs绝对值:

ratings['diff'] = abs(ratings['rating'] - ratings['avg_rating'])

完整的两段代码为:

import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
ratings.sort_values(by='timestamp', inplace=True)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = abs(ratings['rating'] - ratings['avg_rating'])
print(ratings[['diff']].mean())

运行结果为:diff    0.824448


import pandas as pd

rnames = ['uid', 'mid', 'rating', 'timestamp']
ratings = pd.read_table('u.data', sep='\t', header=None, names=rnames)
#打乱ratings次序
ratings = ratings.sample(frac=1)
ratings.reset_index(drop=True, inplace=True)
# 计算每个电影的历史平均评分(当前行之前)
ratings['avg_rating'] = ratings.groupby('mid')['rating'].apply(lambda x: x.expanding().mean().shift()).reset_index(level=0, drop=True)
ratings['diff'] = abs(ratings['rating'] - ratings['avg_rating'])
print(ratings[['diff']].mean())

运行结果为:diff    0.828603

这次打乱后可以看出变大了,说明时序数据存在更小的差值,存在一致性!我重新结合这些发现,再次提问:

其实你说的不对,我发现在计算每个用户评分与电影历史评分均值的差值平均时,忘记加了abs,导致数值正负抵消,因此应该写为:ratings1['diff'] = abs(ratings1['rating'] - ratings1['avg_rating']) ratings1 = ratings1[['uid', 'diff']].groupby('uid').mean().sort_values(by='diff', ascending=False) 此时会发现打乱后数值更大,说明越背离电影历史平均值

大语言模型的结果为:

你说得完全正确!我之前确实犯了一个严重的错误——忘记取绝对值了。你的发现非常关键,这彻底改变了对结果的理解!

为什么必须取绝对值?

错误的做法(我之前给的代码):

python

ratings['diff'] = ratings['rating'] - ratings['avg_rating']  # 有正有负
mean_diff = ratings['diff'].mean()  # 正负抵消 → -0.0135

问题:正负值相互抵消,掩盖了真实的偏离程度。

正确的做法(你提出的):

python

ratings['diff'] = abs(ratings['rating'] - ratings['avg_rating'])  # 全部为正
mean_diff = ratings['diff'].mean()  # 真实反映了偏离程度

好处:每个用户的偏离程度都被保留,不会被正负抵消。


你的发现揭示了什么?

根据你的逻辑:

版本 处理方式 diff均值(取绝对值后) 含义
时序 按时间排序 较小 用户评分更靠近电影历史平均分(一致性更强)
打乱 随机打乱 较大 用户评分更远离电影历史平均分(一致性被破坏)

核心结论

打乱时序后,用户评分与电影历史平均分的绝对偏差更大,说明一致性确实存在——当用户能看到历史评分时,他们的评分会被"拽"向均值。

显然,对于这个错误,大语言模型也并没有坚持以前的说法,勇于承认了。

我把这次交互的理解总结下:

1)大语言模型的幻觉问题随着技术发展,可能表现为越来越不易发现,即对于存在错误的观点,它凭借其强大的推理能力,反而能找到合适的理由,甚至一些让人都觉得很有意思和带有启发性的理由,这一点,也可以给我们很好的启发。但是关键问题在于,这个理由未必一定正确,用户一定要寻根探底,比如本例中的关键理由在于大语言模型认为早期电影评分更高,这其实并不对。但是如果是更全的数据,也确实表现出这个特征,一定程度上大语言模型说的也有一定的道理。但是如果用户自己不较真,盲目相信,大语音模型扮演的更像一个极其博学且又巧舌如簧的辩手,不断的证明自己是正确的。这一点是尤其要让我们值得警惕的!

2)从这个案例中,我们也看到了人在核心创新思考中的价值。个人专业能力和对于问题(包括背景等)的了解非常重要,否则即使觉得哪里有问题,也很难界定哪里有问题,更无法有效的提出可能的调整思路。虽然这几年人们不断地听到AI帮助人类解决了很多创新问题,但是从我自己的经验来看,我想这一定是少数,大部分的AI创新,受限于对外部数据和背景知识的缺乏,在推理过程会强行寻找可行的理由,从而导致结果存疑。这一点仍然需要具有专业知识的人来最终确定和排查!

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐