原文:《Building a Next Best Action model using reinforcement learning》 Ilya Katsov, May 15, 2019 • 19
现代的客户分析和个性化系统,使用多种方法来帮助揭示和量化客户的偏好和意图,从而使营销信息、广告、优惠和推荐更加切合和吸引人。但是,大多数这些方法仅用于与客户一次即时性的互动优化,并使用以指标(如点击率(CTR)或转化率(CR))所定义的目标功能。这种方法可能不是真实的客户体验历程的良好近似,后者通常由多个相关的交互组成,可以结合长期目标共同进行优化。在零售银行和电信等行业中,客户关系会长期演变,这一问题尤其重要。
在本教程中,我们将讨论如何将传统的定位和个性化模型(例如相似和协作过滤)与强化学习相结合,以优化多步营销行动策略(又称为Next Best Action策略)。我们使用的方法最初是由Adobe和阿里巴巴开发和测试的,并且在许多实际使用案例中被证明是有效的。在本教程中,我们选择使用带有综合数据的简单测试平台环境来更清晰地解释模型的设计和实现;我们将在另一篇博客文章中介绍实际案例研究。
1. 客户意图分析的基础
为了将营销活动的多步骤优化问题放到(营销)环境中,让我们从最基本且使用最广泛的推荐方法之一开始look-alike(相似人群拓展)算法建模。Look-alike建模的思想是基于给定客户与过去表现出某些可取或不可取属性的其他客户的相似性,个性化广告或优惠。比如一个典型的订阅服务提供商示例,该示例试图通过分发保留优惠来防止客户流失。假设每个优惠都与提供商的成本相关联,我们应该仅针对具有高流失风险的有限客户群。我们可以通过收集大量历史客户资料来解决这个问题,这些资料可以包括人口统计特征和行为特征,并将这些特征与观察到的结果(客户流失或无客户流失)联系起来,训练一个基于这些样本的分类模型,然后使用这个模型对任何给定客户的客户流失风险水平进行评分,以确定是否应该发出优惠。
这种方法提供了很大的灵活性,即根据结果标签的定义,可以为各种各样的业务目标建立模型:可以是尝试新产品的倾向、增加销售的倾向、接近某个品牌或渠道以及许多其他。它也适用于各种环境,包括在线广告和零售促销。
可以以多种方式扩展上述解决方案。基本look-alike建模的明显局限性之一是,针对不同结果和目标的模型是分别构建的,而有关优惠之间相似性的有用信息将被忽略。在提供大量产品的环境(例如推荐系统)中,此问题变得至关重要。通常,通过将用户特征,优惠特征(Offering features)和更大的用户优惠的互动矩阵合并到模型中来规避问题,以便可以在优惠之间推广交互模式。它可以通过许多协作式过滤算法来完成,包括基于矩阵分解,分解机和深度学习方法的算法:
这些方法通过合并更广泛的数据(包括从文本和图像中提取的特征)来帮助更准确地预测客户的意图,但这对于优化多步操作策略并不是特别有用。
战略优化问题可以通过更仔细地设计目标变量来部分解决,这组技术代表了基本look-alike建模的第二个重要扩展。通常将目标变量设计为量化某些立即事件(如点击,购买或取消订阅)的可能性,但它也可以包含更多的战略考虑因素。例如,通常将look-alike模型与生命周期价值(LTV)模型相结合,不仅可以量化事件的可能性,还可以量化事件的长期经济影响(例如,客户从客户流失中获得的总收益是多少)或优惠后三个月内的支出增加):
这些技术有助于将建模过程置于战略环境中,但实际上并没有提供用于优化长期营销传播策略的框架。因此,我们的下一步将是针对此问题开发更合适的框架。
2. 客户历程作为马尔可夫决策过程
营销沟通的战略性(多步骤)优化问题源于客户关系的状态性以及营销行为之间的依存关系。例如,您可以将零售优惠目标定位视为一步一步的过程,在此过程中,客户可以转化,也可以完全失去客户(并训练最大化转化可能性的模型):
但是,实际的零售环境更加复杂,在转换发生之前,客户可以与零售商多次互动,并且客户进行相关购买。请考虑以下促销活动方案:
- 通过广告告知客户产品功能和其他套餐。
- 宣布一项特别优惠,通过在第二次购买时提供折扣来激励客户购买产品(“购买X,并节省您的下一次购物之旅”)。
- 首次购买后,会激励客户进行第二次购买以兑换特价。
显然,以上策略中的所有活动都是相关的,它们的顺序很重要。例如,仅初始广告可能不会增加转化,但可以提高下游优惠的效率。如果活动案例不是在主题时直接开始发布广告,而是在条件要约公告和优惠之间发布广告,则该广告也可能是最有效的。
客户体验历程的复杂结构在银行和电信等行业中扮演着更为重要的角色。在零售银行业务中,客户可以从基本产品开始,例如支票账户,然后开设信用卡账户,然后申请经纪服务或抵押;客户的成熟度会随着时间的增长而增长,因此需要对优惠进行正确排序以解决此问题。
上面的用例可以自然地表示为马尔可夫决策过程(MDP),在此过程中,客户可能处于几种不同的状态,并在营销活动的影响下随着时间的推移从一个状态转移到另一个状态。在每个状态下,营销人员都必须选择一个要执行的动作(或不执行),并且状态之间的每个转换都对应于某种奖励(例如购买次数),以便沿着客户轨迹的所有奖励加起来总计为总回报对应于客户LTV或广告系列ROI:
尽管可以使用具有清晰语义的手工制作的状态集,但我们将假定状态仅由客户特征向量表示,因此在实值特征的情况下,状态总数可以大或无限。注意,在任何时间点的状态都可以包括对该客户采取的所有先前操作的记录。
在MDP框架中,我们的目标是找到最佳策略 π π π,将每个状态映射到选择每个可能动作的概率,以便 π ( a ∣ s ) π(a|s) π(a∣s)是采取行动的的概率 a a a,假设客户当前处于状态 s s s。可以使用该策略下的预期收益(每一次转换所获得的预期奖励 r r r总额)来量化策略的最优性,。
一个简单的解决方案是为不同的动作和训练标签的不同设计构建多个 look-alike模型,类似于以下内容:
在这种方法中,我们首先建立一组模型 M 11 M11 M11、 M 12 M12 M12、…,对于初始状态下允许的每个操作,请使用过去收到此操作的客户的资料,并在很长一段时间内定义目标标签。该标签是预期收益的直接估计。然后,我们建立第二组模型 M 21 M21 M21、 M 22 M22 M22…,使用具有表明要首先执行哪个操作的功能的客户资料,依此类推。换句话说,我们使用给定客户特征的“良好”客户轨迹始于“良好”初始行为这一事实来顺序优化操作。
但是,这种缺乏经验的方法在计算上效率低下,需要建立多个模型,并且不能轻松地用于在线学习。我们可以期望通过使用解决一般MDP问题的强化学习算法来获得更高的效率和灵活性。
3. 使用拟合Q迭代求解MDP
为了应用通用强化学习算法,让我们介绍一些基于MDP框架以及我们先前定义的状态,动作,奖励和策略的概念的附加概念。
假设我们有一些策略
π
π
π,让我们介绍一下行动价值函数
Q
π
Q_π
Qπ,量化在
s
s
s状态采取行动
a
a
a的价值,作为遵循策略
π
π
π,采取行动
a
a
a,从
s
s
s开始的预期收益:
Q
π
(
s
,
a
)
=
E
[
∑
t
>
t
s
r
t
∣
s
,
a
]
Q_π(s,a) = E[\sum_{t>t_s}r_t|s,a]
Qπ(s,a)=E[t>ts∑rt∣s,a]
可以根据在策略
π
π
π下收集的反馈数据(在我们的案例下为客户轨迹)估算行动价值函数 。同时,可以根据估计的行动值函数来改进策略本身。最基本的方法是简单地在每个状态下采取具有最大期望值的行动(因此,此状态下所有其他操作的概率将为零):
π
(
s
)
=
a
r
g
m
a
x
a
Q
π
(
s
,
a
)
π(s) = \underset{a}{argmax} Q_π(s,a)
π(s)=aargmaxQπ(s,a)
因此,学习最佳策略的过程可能是迭代的:我们可以从一些基准策略开始,收集其下的反馈数据,从该数据中学习行动价值函数,更新策略并再次重复该循环。
接下来,我们需要一种用于动作值函数估计的算法。强化学习的理论提供了各种各样的算法,这些算法是针对MDP环境的不同假设而设计的。在最基本的情况下,可以假设一个完全已知的,确定性的和计算易处理的环境,以便所有状态,动作和奖励都是已知的。在这种情况下,可以使用经典的动态编程直接估算操作值函数:我们只需将不同状态的总收益计算为源自这些状态的MDP网格中路径上的报酬之和即可。它甚至不是机器学习问题,而是纯粹的计算问题。然而,
- 概括。状态或转换的数量可以非常大或无限。在这种情况下,我们拥有的训练数据(轨迹)可能无法涵盖所有状态,因此我们需要使用机器学习技术来概括我们的观察结果。
- 在线学习和开发。我们可能只有很少的历史数据,甚至没有历史数据,并且通过反复试验在线学习奖励值和状态。这就导致了勘探开发问题:我们花更多的时间去探索环境并学习什么有效和什么无效,花更少的时间根据我们学到的最佳政策通过采取最佳行动而获得高回报。
- 非政策评估和学习。正如我们前面提到的,策略学习通常是一个增量过程,在该过程中,策略测试和操作值功能的重新估算将反复进行。在包括营销在内的许多环境中,我们测试任意策略的能力有限,因为它们可能导致不适当的客户体验或其他问题。这意味着我们可能需要根据其他策略下生成的反馈数据来学习和评估策略。
所有这些挑战都与我们的用例相关:客户状态由实值特征向量定义,从而使状态总数不受限制,并且即使我们拥有历史数据,我们通常也需要测试和调整针对实际客户的政策首先。
跨状态泛化的第一个问题是通过许多不同的强化学习算法解决的。实际上,当我们讨论用于多步优化的朴素算法时,我们已经看到可以使用look-alike模型来完成这种概括。针对此问题的更有效和通用的解决方案是拟合Q迭代(FQI)算法。
FQI算法的思想是将学习动作值函数的问题减少为监督学习问题。这是通过迭代应用一些监督学习算法来完成的。我们首先训练模型以根据状态和动作特征预测即时回报,然后训练第二个模型以预测未来两个时间步长的回报,依此类推。
更具体地说,让我们假设在某种基线策略下收集了许多轨迹,并将这些轨迹切分为一组单独的转换,每个转换都是初始状态(解释:与之前无关,要未来的价值),动作,奖励和新状态的元组:
T = ( s , a , r , s ′ ) T = {(s,a,r,s')} T=(s,a,r,s′)
FQI算法可以描述如下:
设定状态
s
i
s_i
si和动作
a
i
a_i
ai是表示状态和动作特征的向量,奖励
r
r
r是标量值,并且
0
≤
γ
≤
1
0≤γ≤1
0≤γ≤1 是一种折现率,可用于控制即时奖励和长期奖励之间的折衷。
该算法非常简单:我们从动作值函数的近视开始,然后在每次迭代中调整训练标签以反向传播奖励。FQI算法可以使用任何监督学习算法来近似 Q Q Q函数; 典型的选择是随机森林和神经网络(神经FQI)。
在下一部分中,我们将使用这种方法构建Next Best Action模型的原型。在后面的部分中,我们还将解决该问题的在线和非政策学习方面。
4. 设置测试平台环境
我们通过设置简单的测试平台环境开始构建Next Best Action模型。首先,让我们假设客户可以在任何时间步长执行以下活动之一:不采取任何措施,不购买就访问网站或商店,或进行购买。我们还假设卖方可以在任何时间为客户提供三个优惠之一,或者决定不提供任何优惠。为了便于说明,我们假设这些优惠的语义分别是广告,小折扣和大折扣。为了完整起见,我们还假设客户具有人口统计和行为特征。在不失一般性的前提下,我们仅使用一项功能,根据年龄段,该功能可以为零或一:
events = [
0, # no action
1, # visit
2 # purchase
]
offers = [
1, # advertisement
2, # small discount
3 # large discount
]
demographic = [
0, # young customers
1 # old customers
]
接下来,假设我们有一些历史数据可用来学习商品定位策略。我们创建一个客户配置文件生成器,以模拟三个历史优惠活动。对于每个客户,此生成器迭代100多个时间步,并在随机的时间点发出三个要约。最初,我们使用随机定位策略,因此无论客户特征如何,所有优惠都有相同的概率。客户行为(即事件概率)随时间而变化,取决于先前收到的优惠,并且还取决于人口统计特征:
k = 100 # number of time steps
m = 3 # number of offers (campaigns)
n = 1000 # number of customers
def generate_profiles(n, k, m):
p_offers = [1 / m] * m # offer probabilities
t_offers = np.linspace(0, k, m + 2).tolist()[1 : -1] # offer campaign times
t_offer_jit = 5 # offer time jitter, std dev
P = np.zeros((n, k)) # matrix of events
F = np.zeros((n, k)) # offer history
D = np.zeros((n, 1)) # demographic features
for u in range(0, n):
D[u, 0] = np.random.binomial(1, 0.5) # demographic features
# determine m time points to issue offers for customer u
offer_times_u = np.rint(t_offer_jit * np.random.randn(len(t_offers)) + t_offers)
for t in range(0, k): # simulate a trajectory
if t in offer_times_u:
F[u, t] = multinomial_int(p_offers) + 1 # issue an offer at time t
event = multinomial_int(get_event_pr(D[u], F[u])) # generate an event at time t
P[u, t] = event
return P, F, D
该生成器生成三个矩阵:
- P = n × ķ P = n × ķ P=n×ķ, 客户事件矩阵
- F = n × ķ F = n × ķ F=n×ķ, 发给客户的优惠矩阵
- d = n × 1 d = n × 1 d=n×1, 客户人口统计特征矩阵
模拟器最重要的部分是客户个人资料到事件概率向量的映射器。我们以如下方式定义此映射器:仅当优惠#1(广告)后跟优惠#3(大幅折扣)时,购买的可能性才会增加:
def get_event_pr(d, f):
f_ids = offer_seq(f) # sequence of offer IDs received by the customer
f_ids = np.concatenate((offer_seq(f), np.zeros(3 - len(f_ids))))
if((f_ids[0] == 1 and f_ids[1] == 3) or
(f_ids[1] == 1 and f_ids[2] == 3) or
(f_ids[0] == 1 and f_ids[2] == 3)):
p_events = [0.70, 0.08, 0.22] # higher probability of purchase
else:
p_events = [0.90, 0.08, 0.02] # default behavior
if(np.random.binomial(1, 0.1) > 0): # add some noise
p_events = [0.70, 0.08, 0.22]
return p_events
我们的目标是建立一个可以识别这种依赖性的模型。为了简单起见,我们在选择事件概率时会忽略人口统计特征,但是我们开发的模型的设计也完全能够学习这些依赖性。
接下来,我们生成并可视化训练和测试数据集:
P, F, D = generate_profiles(n, k, m) # training set
Pt, Ft, Dt = generate_profiles(n, k, m) # test set
visualize_profiles(P)
visualize_profiles(F)
我们可以在顶部的图表中看到购买的频率随着时间的推移而增加(黄色虚线的密度从左到右增加),因为一些客户获得了正确的优惠顺序,从而触发了购买可能性的增加。
5. 估计行动值函数
Fitted Q Iteration (FQI)是一种采用经验回放的 Q-learning 的批量强化学习方法。
我们的下一步是使用我们之前介绍的FQI算法学习优惠定位策略。FQI可用于单个转换,因此我们需要将先前生成的轨迹分成以下几部分:
这样,每个客户轨迹都会生成几个转换元组,并且所有元组都合并到一个训练集中。我们选择使用每个转换中的购买次数作为奖励指标,并将每个状态表示为以下特征的向量:人口统计特征,从轨迹开始的总访问次数和提供以下信息的时间步长每种类型均发出:
# p, f, d - rows of matrices P, F, and D that correspond to a given customer
# t_start, t_end - time interval
def state_features(p, f, d, t_start, t_end):
p_frame = p[0 : t_end]
f_frame = f[0 : t_end]
return np.array([
d[0], # demographic features
count(p_frame, 1), # visits
index(f_frame, 1, k), # first time offer #1 was issued
index(f_frame, 2, k), # first time offer #2 was issued
index(f_frame, 3, k) # first time offer #3 was issued
])
def frame_reward(p, t_start, t_end):
return count(p[t_start : t_end], 2) # number of purchases in the time frame
以下代码段包含将轨迹切成转换的完整代码。
我们使用它来准备转换的训练和测试集:
T = prepare_trajectories(P, F, D)
Tt = prepare_trajectories(Pt, Ft, Dt)
数据准备好后,我们可以应用FQI算法学习动作值函数。我们使用scikit-learn中的随机森林算法作为 Q Q Q函数估计:
def Q_0(sa):
return [1]
Q = Q_0
iterations = 6
for i in range(iterations): # FQI iterations
X = []
Y = []
for sample in T.reshape((n * (m + 1), m + 1)):
x = np.append(sample[0], sample[1]) # feature vector consists of state-action pairs
a_best, v_best = best_action(Q, sample[3], offers)
y = sample[2] + v_best # reward + value
X.append(x)
Y.append(y)
regr = RandomForestRegressor(max_depth=4, random_state=0, n_estimators=10)
regr.fit(X, np.ravel(Y))
Q = regr.predict
# find the optimal action under a greedy policy and corresponding state value
def best_action(Q, state, actions):
v_best = 0
a_best = 0
for a in actions:
v = Q([np.append(state, a)])[0]
if(v > v_best):
v_best = v
a_best = a
return a_best, v_best
接下来,我们可以使用操作值功能为测试集提出“下一步最佳活动”建议。可视化这些建议的操作和相应的行动值函数估计是很有见地的:
上方的散点图显示了每个客户状态的最佳操作的值,下方的散点图显示了针对每个状态的建议的次佳操作。除颜色编码外,这两个图相同。我们可以看到具有不同期望值和建议操作的多个客户状态(细分)集群。如前所述,模型中的每个状态都由五个特征的向量表示(人口统计,三个提议的访问次数和发布时间),因此从每个细分中抽取一些样本并进行检查非常有用他们:
- 第1部分。这些是在轨迹开始时获得优惠3的客户。我们的生成模型仅在优惠#1优先于优惠#3的情况下才提高购买概率,因此这些客户失去了价值最小的原因。
- 第2部分。这些客户首先获得了优惠2。该模型正确地建议接下来给他们提供#1,但是由于到轨迹结束为止的剩余时间有限,因此该细分的预测值相对较低。
- 第3部分。这些客户首先获得了优惠1,并且模型建议接下来给他们提供优惠3。期望值相对较高,因为这些客户处在正确的轨道上。
- 第4部分。这些客户已经获得了优惠1和优惠3,并提高了购买率。它们具有最高的期望值。系统建议向他们提供商品1作为默认操作,但是建议的操作没有任何区别,因为那时正确的商品组合已经被解锁。
6. 评估下一个最佳行动政策
FQI算法提供了一种根据在某些先前策略下获得的历史数据来估算作用值函数的方法。我们需要将操作值函数转换为“下一个最佳活动”策略,可以通过几种不同的方式来完成此操作。我们已经看到,可以通过执行具有最高期望值的活动并将在给定状态下允许执行任何其他操作的概率为零来完成此操作。这种方法会产生一个贪婪的策略,该策略只专注于根据当前已知的策略获得奖励,并且不会探索替代(非最优)操作来改进策略。
由于学习过程通常是交互式的,甚至是在线的,并且在FQI生成的第一个策略下获得的数据随后将用于学习下一个策略,因此通常使用epsilon-greedy策略来随机探索一些超参数定义的小概率
ϵ
ϵ
ϵ:
π
ϵ
(
s
,
a
)
=
{
1
−
ϵ
,
i
f
a
=
a
r
g
m
a
x
a
Q
π
(
s
,
a
)
ϵ
/
(
m
−
1
)
,
o
t
h
e
r
w
i
s
e
π_ϵ(s,a)=\left\{\begin{matrix} 1-ϵ ,& if a = \underset{a}{argmax} Q_π(s,a)\\ ϵ/(m−1), & otherwise \end{matrix}\right.
πϵ(s,a)={1−ϵ,ϵ/(m−1),ifa=aargmaxQπ(s,a)otherwise
m m m是该状态允许的行动总数。这为探索和策略改进提供了更多带宽。在动态在线环境中,该策略可以多次更新,并且参数 ϵ ϵ ϵ也可以根据观察到的反馈的波动性动态调整。
需要解决的第二个问题是在将该策略应用于生产之前,在优化策略下估算预期收益。可能需要确保新策略满足服务质量和性能标准,还可能需要调整新策略的参数,例如 ϵ ϵ ϵ 在贪婪 ϵ ϵ ϵ策略中。
可以使用以前的策略生成的历史轨迹来完成此策略评估,并根据策略之间的差异按比例调整收益。假设我们使用一些基线策略生成了一条轨迹
π
0
π_0
π0由一系列状态
s
t
s_t
st和行动
a
t
a_t
at组成,我们可以估算出新策略
π
π
π的回报如下:
R
^
(
π
)
=
R
(
π
0
)
∏
t
π
(
s
t
,
a
t
)
π
0
(
s
t
,
a
t
)
\widehat{R}(π) = R(π_0) \prod_{t} \frac {π(s_t,a_t)} {π_0(s_t,a_t)}
R
(π)=R(π0)t∏π0(st,at)π(st,at)
求积
R ( π 0 ) R(π_0) R(π0)是观察到的轨迹返回。在一系列轨迹上平均此估计,我们可以评估策略的总体预期绩效。这种方法称为重要性抽样估计。
使用重要性采样执行策略评估非常简单:
def evaluate_policy_return(T, behavioral_policy, target_policy):
returns = []
for trajectory in T:
importance_weight = 1
trajectory_return = 0
for transition in trajectory:
state, action, reward = transition[0 : 3]
action_prob_b = behavioral_policy(state, action)
action_prob_t = target_policy(state, action)
importance_weight *= (action_prob_t / action_prob_b)
trajectory_return += reward
returns.append(trajectory_return * importance_weight)
return np.mean(returns)
我们使用此评估程序来量化epsilon-greedy政策的预期效果如何取决于 ϵ ϵ ϵ 参数:
policy_values = []
eps = np.linspace(0, 2/3, num = 10)
for e in eps:
policy = make_epsilon_greedy_policy(e)
policy_values.append( evaluate_policy_return(T, behavioral_policy, policy) )
plt.plot(eps, policy_values)
plt.show()
重要采样和其他非策略评估方法提供了一种在生产中部署新策略之前进行安全检查和修改的方法。
7. 结论
强化学习为构建Next Best Action模型提供了一个方便的框架,该模型通常需要结合预测分析,组合优化和动态环境的主动探索。以不同方式组合这些元素的能力是强化学习框架的主要优势之一。例如,使用多臂强盗算法可以解决只需要实验的基本优惠优化问题。然后,可以使用上下文盗贼合并有关客户和优惠的信息,然后可以使用本文所述的通用强化学习算法来制定更具战略意义的决策。最后,可以使用深度强化学习方法(例如DQN)创建更复杂的模型。
强化学习方法的第二个优点是建模的灵活性。与蒙特卡洛模拟相似,它可以通过重新定义奖励函数或行动集来合并其他要求,例如预算约束,运营成本和业务约束。与手动将优化问题转换为某种标准形式(例如线性或整数编程)相比,这提供了更大的灵活性。
相同的方法可以应用于企业运营的许多其他领域,包括营销,定价和物流。例如,在之前的博客文章中,我们讨论了如何将多臂匪用于动态价格优化,并且可以使用MDP框架进一步采用这种方法。
本文描述的测试平台环境的完整实现可在github上找到。我们仅使用普通的python和scikit-learn来实现我们的玩具模型,但是强烈建议使用诸如OpenAI Gym之类的强化学习框架进行实际生产实现,以可靠地处理建模和优化的各个方面和边缘情况。
参考文献
G. Theocharous, P. Thomas, and M. Ghavamzadeh, Personalized Ad Recommendation Systems for Life-Time Value Optimization with Guarantees, 2015
D. Wu, X. Chen, X. Yang, H. Wang, Q. Tan, X. Zhang, J. Xu, and K. Gai, Budget Constrained Bidding by Model-free Reinforcement Learning in Display Advertising, 2018
D. Ernst, L. Wehenkel, and P. Geurts, Tree-based batch mode reinforcement learning. Journal of Machine Learning Research, 2005
M. Riedmiller, Neural Fitted Q Iteration - First Experiences with a Data Efficient Neural Reinforcement Learning Method, 2005
V. Mnih et al, Human-level Control Through Deep Reinforcement Learning, 2015
由于本人水平有限,本文翻译基于Google自动翻译,专业词汇和术语表达可能不准,意思不准,欢迎交流反馈。