强化学习案例:美团是如何在推荐系统中落地强化学习

目录

美团的强化学习应用场景和分析

场景举例

使用原因

强化学习的六大要素

智能体

环境

行动

奖励

目标

状态

美团强化学习模型设计

美团强化学习工程落地

总体的数据结构关系图

实现步骤

1. 日志收集与实时处理(Log Collector, Online Joiner)

2. 经验回放(Experience Collector)

3. 离线训练(Offline Training)

4. 在线学习(Online Training)

5. 模型服务(Model Serving)

补充说明

1. 特征数据(FeatureData)

2. 状态表示(State)

3. 在线服务中的模型计算

4. 模型更新与版本管理

5.为什么要离线和在线训练相结合

相关资料


美团的强化学习应用场景和分析

场景举例

“猜你喜欢”是美团这个团购 App 中流量最大的推荐展位,产品形态是信息流。从图 1的 App 截图中你可以看到,“猜你喜欢”列表中推荐了用户可能喜欢的餐厅,用户可以通过下滑和翻页的方式实现与 App 的多轮交互,在这个过程中,用户还可能发生点击、购买等多种行为。

强化学习的应用场景就藏在用户和美团 App 的多轮交互之中。如果推荐系统能够在用户每次翻页的时候都考虑到用户刚刚发生的行为,就能够提供实时性更强的推荐体验。图 2 是美团的同学统计的用户翻页次数的分布图,我们可以看到,多轮交互确实是美团 App 中非常常见的用户场景,这也说明强化学习还是非常有用武之地的。
猜你喜欢”展位用户翻页情况统计

使用原因

传统的推荐算法,如协同过滤、矩阵分解等,在很多场景下已经可以取得不错的效果。那么,我们为什么还要引入强化学习呢?主要有以下几点原因:

  • 强化学习能够建模多轮交互场景。传统推荐算法通常是根据用户的长期历史行为和兴趣来进行建模,给出一次性的推荐结果。但在美团"猜你喜欢"这样的场景下,用户可以与推荐列表进行多轮交互,如翻页、点击等。每一轮交互后,用户的即时反馈实际上反映了用户当前的意图和偏好,需要推荐系统及时捕捉并调整策略。强化学习通过奖励机制可以很好地建模这种动态变化的过程。

  • 强化学习可以优化长期收益。传统算法通常是针对眼前的指标(如预测准确率)进行优化。但推荐系统的最终目标是提升用户的长期参与度、留存率和满意度。强化学习的目标恰恰是最大化长期累积奖励,与这个目标更加一致。通过折扣因子等机制,强化学习能权衡当前和未来收益,避免短视行为。

  • 强化学习可以灵活纳入各种额外信息。用户行为是在一个复杂多变的真实环境中产生的,与用户自身的动机和场景高度相关。强化学习可以将场景特征、时间段、用户属性等各种额外信息都建模到状态表征中,捕捉用户行为的背景,给出更精准、个性化的推荐。传统算法对场景建模的能力相对有限。

  • 强化学习能够平衡探索和利用。推荐系统需要在向用户推荐他们已知的感兴趣的物品(利用),和探索用户可能感兴趣的新物品(探索)之间权衡。过度利用会让用户觉得推荐单一乏味,过度探索又会损害用户体验。强化学习通过ε-贪心等策略,可以在二者间找到平衡,并随时调整。

  • 当然,强化学习也不是万能的。它对样本数据的要求很高,训练过程不够稳定,落地的工程复杂度高。因此现实中需要根据具体的场景特点,来权衡是否值得引入强化学习。像美团这样有足够数据和工程能力的大型推荐系统,使用强化学习可以进一步提升推荐的效果。但对于很多中小型系统,传统算法可能依然是更现实的选择。

强化学习的六大要素

智能体

美团的推荐系统

环境

美团App的"猜你喜欢"推荐场景

行动

生成推荐商品列表

奖励

用户在推荐列表上的点击、下单行为的加权和

奖励”指的是推荐系统把推荐列表推送给用户之后,用户在这个列表之上的反馈。对于美团来说,“猜你喜欢”展位的核心优化指标是点击率和下单率,这里的奖励就可以被定义成推荐列表中物品的点击次数和下单次数的加权和。如下面的公式所示,其中的 wc 和 wp 分别是点 击次数和下单次数的权重,这两个超参数可以根据你对它们的重视程度进行调整。

目标

最大化多轮交互后的累积奖励

它的形式化表达如下所示:

  • s 表示当前状态(state)
  • a 表示在状态 s 下采取的动作(action)
  • t 表示当前所处的时间步(time step)或决策阶段
  • r_{t+k} 表示在时间步 t+k 时获得的奖励(reward)
  • γ (gamma) 是折扣因子(discount factor),0 <= γ <= 1,用于控制未来奖励的重要程度。γ 越大,则表示越重视长远的奖励

状态

通过深度学习网络生成的综合表征用户意图、兴趣、场景的embedding向量

状态建模网络结构(下往上看)
这里比较有意思的是第一个部分,它采用了单层 CNN,也就是卷积神经网络来学习用户的实 时意图表达,这是我们之前没有遇到过的处理手法。
那它是怎么做的呢?它首先把用户交互获得的 Item Embedding 组成了一个矩阵,然后在这 个矩阵之上使用了两个不同尺度的卷积层,再对卷积层中的每个行向量进行池化操作,生成两 个代表了用户实时意图的 Embedding,把它们与第二部分的场景特征向量连接后,再经过全连接层,生成最终代表状态的 State Embedding。

美团强化学习模型设计

  • 采用一个类似DQN的actor-critic架构作为核心的强化学习模型:
  1. Critic网络(Advantage函数):评估在某状态下采取某行动的优劣
  2. Actor网络(Value函数):评估某状态下的期望long-term收益
美团强化学习模型
  • 线上线下结合的工程架构:
  1. 离线进行深度模型的预训练,如状态embedding网络
  2. 线上进行实时的数据处理和模型finetune,动态优化模型
  3. 针对性地优化线上serving的效率,如embedding层剥离、预热等
  • 整个推荐流程与数据流、训练、serving等环节紧密结合,形成一个完整的闭环

美团强化学习工程落地

总体的数据结构关系图

实时更新的强化学习框架

原始日志数据(String,JSON 格式)
        |
        v
解析后的日志事件(LogEvent)
        |
        v
样本数据(Sample)<--(在线特征 Join)-->特征数据(FeatureData)
        |
        v
经验数据(Experience)(经过会话窗口和序列处理)
        |
        v
离线训练和在线学习(使用 Sample 和 Experience 进行训练)
        |
        v
模型参数(State Network、Advantage Network、Value Network、Embedding Layer)
        |
        v
模型服务(加载模型参数,提供在线推荐服务)

实现步骤

1. 日志收集与实时处理(Log Collector, Online Joiner)

使用 Flink 作为流处理框架,从 Kafka 中消费日志数据,进行实时数据处理和特征合并,然后将处理后的样本数据写回 Kafka 或下游存储。

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer// 创建 Flink 流执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment// 配置 Kafka 消费者参数
val kafkaConsumerProps = new Properties()
kafkaConsumerProps.setProperty("bootstrap.servers", "localhost:9092")
kafkaConsumerProps.setProperty("group.id", "log_consumer_group")// 创建 Kafka Source,用于消费日志数据
val logSource = new FlinkKafkaConsumer[String]("log_topic",new SimpleStringSchema(),kafkaConsumerProps
)// 从 Kafka 中读取日志数据
val logStream: DataStream[String] = env.addSource(logSource)// 对日志数据进行解析和预处理
val parsedLogStream: DataStream[LogEvent] = logStream.map(parseLog) // 自定义的日志解析函数,将字符串解析为 LogEvent 对象// 配置 Kafka 生产者参数
val kafkaProducerProps = new Properties()
kafkaProducerProps.setProperty("bootstrap.servers", "localhost:9092")// 创建 Kafka Sink,用于输出处理后的样本数据
val sampleSink = new FlinkKafkaProducer[Sample]("sample_topic",new SampleSerializationSchema(),kafkaProducerProps,FlinkKafkaProducer.Semantic.AT_LEAST_ONCE
)// 进行在线特征 Join(假设 featureStream 是已存在的特征数据流)
val featureStream: DataStream[FeatureData] = ...// 在线 Join,将日志数据与特征数据合并
val sampleStream: DataStream[Sample] = parsedLogStream.keyBy(_.userId).connect(featureStream.keyBy(_.userId)).process(new OnlineJoiner()) // 自定义的 ProcessFunction,实现在线 Join// 将合并后的样本数据写入 Kafka
sampleStream.addSink(sampleSink)// 启动 Flink 作业
env.execute("Log Collector and Online Joiner")

注意:

  • parseLog 是自定义的函数,用于解析原始日志字符串为 LogEvent 对象。
  • OnlineJoiner 是自定义的 CoProcessFunction,实现了两个流的在线 Join 操作。
  • SampleSerializationSchema 是自定义的序列化方案,用于将 Sample 对象序列化为字节数组写入 Kafka。

数据流动过程:

  • 原始日志数据从 Kafka 输入,类型为 String
  • 使用 parseLog 函数将原始日志字符串解析为 LogEvent 对象。
  • 与特征数据流(FeatureData)进行在线 Join,生成 Sample 数据。
  • Sample 数据写入下游系统(如 Kafka、存储系统等)。
case class LogEvent(userId: String,        // 用户IDitemId: String,        // 物品IDaction: String,        // 用户行为类型,例如点击、购买timestamp: Long        // 行为发生的时间戳// 其他可能的字段,例如客户端信息、位置信息等
)case class Sample(userId: String,                // 用户IDitemId: String,                // 物品IDuserFeatures: Map[String, Double], // 用户特征,键值对形式itemFeatures: Map[String, Double], // 物品特征,键值对形式contextFeatures: Map[String, Double], // 上下文特征,键值对形式action: String,                // 用户行为label: Double,                 // 标签,例如点击为1,未点击为0timestamp: Long                // 行为发生的时间戳
)

2. 经验回放(Experience Collector)

使用 Flink,对在线 Join 的样本数据进行会话窗口划分,生成强化学习模型训练所需的序列数据。

import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows
import org.apache.flink.streaming.api.windowing.time.Time// 假设 sampleStream 是上一步产生的样本数据流
val sampleStream: DataStream[Sample] = ...// 设置时间特征为事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)// 对样本数据进行水位线设置,以支持基于事件时间的窗口操作
val timestampedSampleStream = sampleStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Sample](Time.seconds(10)) {override def extractTimestamp(element: Sample): Long = element.eventTime})// 按照用户 ID 进行会话窗口划分
val sessionWindows = timestampedSampleStream.keyBy(_.userId).window(EventTimeSessionWindows.withGap(Time.minutes(30)))// 在会话窗口内收集经验数据
val experienceStream: DataStream[Experience] = sessionWindows.process(new ExperienceCollector())// 将经验数据输出到下游(如写入 Kafka、存储到文件系统等)
experienceStream.addSink(....)

注意:

  • ExperienceCollector 是自定义的 ProcessWindowFunction,用于在会话窗口内收集用户交互序列,生成 Experience 对象。
  • 需要确保样本数据流中包含事件时间戳,并正确设置水位线(Watermarks)以支持事件时间窗口。

数据流动过程:

  • 样本数据按照 userId 进行会话窗口划分,形成用户的行为序列。
  • 在会话窗口内,使用 ExperienceCollector 将样本数据组装为强化学习的序列数据,即 Experience 对象。
  • Experience 数据发送到下游系统(如用于在线训练的 Kafka 主题或存储系统)
case class Experience(userId: String,                   // 用户IDstateSeq: Seq[State],             // 状态序列actionSeq: Seq[String],           // 动作序列rewardSeq: Seq[Double],           // 奖励序列nextStateSeq: Seq[State],         // 下一个状态序列doneSeq: Seq[Boolean],            // Episode 是否结束的标记序列timestampSeq: Seq[Long]           // 时间戳序列
)// 定义状态
case class State(userFeatures: Map[String, Double],    // 用户特征itemFeatures: Map[String, Double],    // 物品特征contextFeatures: Map[String, Double]  // 上下文特征
)

3. 离线训练(Offline Training)

使用 TensorFlow 离线训练相对稳定的模型部分,如状态网络(State Network)和 Embedding 层。

import tensorflow as tf# 定义状态网络结构
def build_state_network(input_shape):inputs = tf.keras.Input(shape=input_shape)x = tf.keras.layers.Dense(128, activation='relu')(inputs)x = tf.keras.layers.Dense(64, activation='relu')(x)outputs = tf.keras.layers.Dense(state_dim)(x)model = tf.keras.Model(inputs, outputs, name='StateNetwork')return model# 定义 Embedding 层
def build_embedding_layer(vocab_size, embedding_dim):embedding_layer = tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, name='ItemEmbedding')return embedding_layer# 建立模型
state_network = build_state_network(input_shape=(feature_dim,))
embedding_layer = build_embedding_layer(vocab_size=item_count, embedding_dim=embedding_dim)# 定义优化器和损失函数
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
loss_fn = tf.keras.losses.MeanSquaredError()# 加载训练数据(假设数据存储在 HDFS 上)
def data_generator():for sample_batch in read_hdfs_samples(hdfs_path):yield sample_batch['features'], sample_batch['labels']# 创建数据集
train_dataset = tf.data.Dataset.from_generator(data_generator,output_types=(tf.float32, tf.float32),output_shapes=((None, feature_dim), (None, state_dim))
).batch(batch_size)# 训练模型
for epoch in range(num_epochs):for step, (features, labels) in enumerate(train_dataset):with tf.GradientTape() as tape:embeddings = embedding_layer(features['item_ids'])inputs = tf.concat([features['user_features'], embeddings], axis=1)predictions = state_network(inputs)loss = loss_fn(labels, predictions)gradients = tape.gradient(loss, state_network.trainable_variables + embedding_layer.trainable_variables)optimizer.apply_gradients(zip(gradients, state_network.trainable_variables + embedding_layer.trainable_variables))print(f'Epoch {epoch + 1}, Loss: {loss.numpy()}')# 保存模型
state_network.save('models/state_network')
embedding_layer.save_weights('models/embedding_layer_weights')# 将训练好的 Embedding 参数保存到在线存储,如 Redis 或 Tair
item_embeddings = embedding_layer.get_weights()[0]
save_embeddings_to_tair(item_embeddings)

注意:

  • read_hdfs_samples 是自定义的函数,用于读取 HDFS 上的样本数据。
  • 需要将训练好的模型和 Embedding 参数保存,以供在线服务使用。

数据流动过程:

  1. 从存储系统读取训练数据,解析出模型所需的特征和标签。
  2. 使用 TensorFlow 构建模型,输入为用户特征和物品特征,目标为预测用户的行为。
  3. 训练模型,调整模型参数。
  4. 训练完成后,保存模型结构和参数。
  5. 提取 Embedding 层的参数(物品 Embedding 向量),存储到在线存储系统(如 Tair、Redis)。
# 假设输入的数据是 TFRecord 格式,包含以下字段
{'user_features': tf.train.Feature(float_list=tf.train.FloatList(value=[...])),  # 用户特征向量'item_features': tf.train.Feature(float_list=tf.train.FloatList(value=[...])),  # 物品特征向量'label': tf.train.Feature(float_list=tf.train.FloatList(value=[...])),          # 标签# 其他可能的字段
}

4. 在线学习(Online Training)

对需要在线更新的网络(如 Advantage Network 和 Value Network),实现流式的在线训练。

import tensorflow as tf# 定义 Advantage Network 和 Value Network
def build_advantage_network(input_shape):inputs = tf.keras.Input(shape=input_shape)x = tf.keras.layers.Dense(64, activation='relu')(inputs)outputs = tf.keras.layers.Dense(action_dim)(x)model = tf.keras.Model(inputs, outputs, name='AdvantageNetwork')return modeldef build_value_network(input_shape):inputs = tf.keras.Input(shape=input_shape)x = tf.keras.layers.Dense(64, activation='relu')(inputs)outputs = tf.keras.layers.Dense(1)(x)model = tf.keras.Model(inputs, outputs, name='ValueNetwork')return modeladv_network = build_advantage_network(input_shape=(state_dim,))
value_network = build_value_network(input_shape=(state_dim,))# 定义优化器和损失函数
adv_optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
value_optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
loss_fn = tf.keras.losses.MeanSquaredError()# 从流中读取经验数据(假设使用 tf.data 从 Kafka 读取)
def experience_data_generator():for exp_batch in read_experience_stream(kafka_topic):yield exp_batch['states'], exp_batch['actions'], exp_batch['rewards'], exp_batch['next_states']# 创建数据集
experience_dataset = tf.data.Dataset.from_generator(experience_data_generator,output_types=(tf.float32, tf.int32, tf.float32, tf.float32),output_shapes=((None, state_dim), (None,), (None,), (None, state_dim))
).batch(batch_size)# 在线训练循环
for step, (states, actions, rewards, next_states) in enumerate(experience_dataset):# 计算目标值with tf.GradientTape() as tape_adv, tf.GradientTape() as tape_value:advantages = adv_network(states)values = value_network(states)next_values = value_network(next_states)td_targets = rewards + gamma * tf.stop_gradient(next_values)advantages_selected = tf.gather_nd(advantages, tf.stack([tf.range(tf.shape(actions)[0]), actions], axis=1))# 计算损失adv_loss = tf.reduce_mean(tf.square(advantages_selected - (td_targets - values)))value_loss = loss_fn(td_targets, values)# 计算梯度并更新参数adv_gradients = tape_adv.gradient(adv_loss, adv_network.trainable_variables)value_gradients = tape_value.gradient(value_loss, value_network.trainable_variables)adv_optimizer.apply_gradients(zip(adv_gradients, adv_network.trainable_variables))value_optimizer.apply_gradients(zip(value_gradients, value_network.trainable_variables))if step % 100 == 0:print(f'Step {step}, Adv Loss: {adv_loss.numpy()}, Value Loss: {value_loss.numpy()}')# 定期保存模型参数if step % 1000 == 0:adv_network.save_weights('models/adv_network_weights')value_network.save_weights('models/value_network_weights')

注意:

  • read_experience_stream 是自定义的函数,用于从 Kafka 中读取 Experience 数据流。
  • 在线训练需要注意数据的稳定性和训练速度,可以考虑使用分布式训练或异步更新等优化策略。

数据流动过程:

  1. 从经验数据流中读取 Experience 数据(与经验回放模块的输出相同)。
  2. 将经验数据解析为用于训练的张量,包括状态、动作、奖励、下一状态等。
  3. 使用在线学习算法(如策略梯度、Q-learning 等)计算损失和梯度。
  4. 更新 Advantage 网络和 Value 网络的模型参数。
  5. 定期保存模型参数,供模型服务模块加载。

5. 模型服务(Model Serving)

TensorFlow Serving 部署模型服务,解决模型热加载和多版本管理的问题。

模型配置:

# 创建模型配置文件 models.config
model_config_list: {config: {name: 'recommendation_model',base_path: '/models/recommendation_model',model_platform: 'tensorflow',model_version_policy: { all: {} }}
}

启动 TensorFlow Serving:

tensorflow_model_server --port=8500 --rest_api_port=8501 \--model_config_file=/path/to/models.config \--file_system_poll_wait_seconds=60

模型热加载和预热:

为了避免模型加载时影响请求处理,可以采用预热策略:

import requests
import json# 模型预热函数
def warm_up_model():# 构造一个 dummy 请求dummy_request = {"signature_name": "serving_default","instances": [dummy_input_data]}# 发送请求到 TensorFlow Servingresponse = requests.post('http://localhost:8501/v1/models/recommendation_model:predict', data=json.dumps(dummy_request))if response.status_code == 200:print('Model warm-up successful.')else:print('Model warm-up failed.')# 在模型部署后进行预热
warm_up_model()

请求处理时的线程池优化:

需要对 TensorFlow Serving 的源码进行修改,分离模型加载与请求处理的线程池。这需要一定的 C++ 开发能力和对 TensorFlow Serving 内部架构的了解。

版本控制和灰度发布:

可以通过在模型路径下放置不同版本的模型,并使用 TensorFlow Serving 的模型版本策略进行控制。

--model_version_policy="{\"specific\": {\"versions\": [1, 2]}}"

模型监控和自动化部署:

  • 集成 Prometheus 或其他监控工具,监控模型的性能和请求情况。
  • 使用 CI/CD 工具(如 Jenkins、GitLab CI)实现模型的自动化构建、测试和部署。

补充说明

1. 特征数据(FeatureData)

在在线 Join 阶段,需要将日志事件与用户特征和物品特征进行合并,特征数据可能来自于缓存、数据库或实时计算。

用户特征(UserFeature)

case class UserFeature(userId: String,featureMap: Map[String, Double]  // 用户特征键值对
)

物品特征(ItemFeature) 

case class ItemFeature(itemId: String,featureMap: Map[String, Double]  // 物品特征键值对
)

2. 状态表示(State)

状态通常是用户特征、物品特征和上下文特征的组合,用于表示在某一时刻用户的"状态"。

状态(State)

case class State(userEmbedding: Vector[Double],      // 用户特征的嵌入向量itemEmbedding: Vector[Double],      // 物品特征的嵌入向量contextFeatures: Map[String, Double] // 上下文特征
)

3. 在线服务中的模型计算

在线服务需要根据输入的数据结构,使用加载的模型参数进行计算。

计算步骤:

  1. 状态表示计算

    使用状态网络(State Network)对用户特征、物品特征和上下文特征进行处理,生成状态表示。

  2. 动作价值评估

    使用 Advantage 网络和 Value 网络,对当前状态下的各个可能的动作(物品)计算价值。

  3. 动作选择

    根据计算的价值,选择排序得分最高的物品作为推荐结果。

4. 模型更新与版本管理

模型服务需要支持模型的热更新和多版本管理,以确保服务的连续性和稳定性。

模型版本目录结构示例:

/models/recommendation_model/1saved_model.pbvariables//2saved_model.pbvariables/

模型服务可以配置自动加载最新版本的模型,并在后台完成模型的加载和预热。

5.为什么要离线和在线训练相结合

  • 模型的稳定性:
    有些模型组件,如状态表征网络(State Network)、物品 Embedding 等,其参数相对稳定,不需要频繁更新。这些组件可以在离线通过大规模历史数据进行充分训练,得到一个性能不错且稳定的模型。离线训练通常批量大、迭代充分,模型更加鲁棒。

  • 实时性和适应性:
    另一些组件,如 Advantage 网络、Value 网络等,其作用是对当前环境下的行为策略做出及时反馈和评估,以指导模型做出调整。这就需要基于用户的实时反馈数据进行更新。在线训练可以让模型快速适应用户偏好的变化,提供更加个性化的推荐。

  • 计算资源的平衡:
    端到端的深度学习模型通常计算量很大,若完全在线训练,对计算资源和响应时间都是巨大的挑战。合理的划分离线和在线训练的边界,可以在保证一定实时性的同时,最大化利用离线计算资源,减轻在线系统的压力。

相关资料

强化学习在美团“猜你喜欢”的实践_文化 & 方法_美团技术团队_InfoQ精选文章

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

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

相关文章

PyTorch 2.5 发布带来一些新特性和改进

官网&#xff1a;https://github.com/pytorch/pytorchGitHub&#xff1a;https://github.com/pytorch/pytorch原文&#xff1a;https://github.com/pytorch/pytorch/releases/tag/v2.5.0 主要亮点 (Highlights)] SDPA CuDNN 后端&#xff1a;为 torch.nn.functional.scaled_d…

C++标准模板库--vector

vector 介绍 vector&#xff08;向量&#xff09;是一种序列容器&#xff0c;表示为可以改变大小的数组。vector中的元素使用连续的存储位置&#xff0c;这意味着也可以使用指向其元素的常规指针偏移量来访问任意元素&#xff0c;且与数组一样高效。但与数组不同的是&#xff…

React Componet类组件详解(老项目)

React类组件是通过创建class继承React.Component来创建的&#xff0c;是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释&#xff1a; 一、定义与基本结构 类组件使用ES6的class语法定义&#xff0c;并继承自React.Component。它们具有更复杂的功能&#…

流量PID控制(开度前馈量计算+辅助PID)

和流体流速(瞬时流量)相关的计算请参考下面文章链接: 1、PLC通过伯努利方程近似计算水箱流量 PLC通过伯努利方程近似计算水箱流量(FC)-CSDN博客文章浏览阅读1.6k次。本文介绍了如何使用PLC通过伯努利方程近似计算水箱中的液体流量,主要涉及流量计算、模型验证、梯形图编程及…

C++学习路线(二十)

项目 模块划分 推箱子游戏 地图初始化 热键控制 推箱子控制 游戏结束 地图初始化 坐标系&#xff08;650&#xff0c;650&#xff09; 地图表示&#xff1a; 使用二维数组 游戏道具展示&#xff08;墙 箱子 箱子目的地 小人 地板&#xff09; 判断游戏…

java基于SpringBoot+Vue+uniapp微信小程序的自助点餐系统的详细设计和实现(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

【H∞鲁棒控制】状态反馈、输出反馈、混合灵敏度H∞控制器设计

最近在学H∞鲁棒控制&#xff0c;因为后面项目中可能要用到此控制方法&#xff0c;所以提前进行了学习&#xff0c;刚开始接触感觉看不太懂&#xff0c;涉及的数学专业知识较深&#xff0c;而且网上资料也比较零星且局限&#xff0c;摸爬了好几天才搞懂了点&#xff0c;现自己总…

「Java服务」快速接入SkyWalking方法指南

一、背景 背景&#xff1a;Apache SkyWalking 是一个开源的分布式应用性能监控&#xff08;APM&#xff09;系统&#xff0c;主要用于监控微服务、云原生和容器化应用的性能。接入SkyWalking可以排查以智能投放服务为主的服务响应问题 技术架构 SkyWalking 的核心架构包括以…

银河麒麟V10系统+Windows10双系统启动顺序正确修改方法

***正确可行方法***&#xff0c;测试OK且稳定&#xff1b; 银河麒麟桌面操作系统V10是一款适配国产软硬件平台并深入优化和创新的新一代图形化桌面操作系统&#xff0c;同源支持国内外主流处理器架构&#xff0c;并不断使能GPU、桥片、网卡等各种新硬件&#xff0c;提供更优的软…

vue3学习之插槽slot

关于slot web组件内部的占位符&#xff0c;可以使用自己的标记填充这个占位符 &#xff0c;具名插槽就是在slot标签上添加name属性&#xff08;https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/slot&#xff09; vue3官方文档&#xff1a;https://cn.vuejs.org/gui…

深入解析Java 22:专栏介绍

深入解析Java 22&#xff1a;专栏介绍 Java&#xff0c;作为一种广泛使用的编程语言&#xff0c;一直以来都在不断地发展和改进。2024年3月19日&#xff0c;Java 22的GA版本正式发布&#xff0c;带来了众多令人瞩目的新特性和性能优化。本专栏将深入解析Java 22&#xff0c;带…

AD9680(adc直采芯片)使用说明

写这篇文章之前我是没有使用过AD9680的芯片&#xff0c;但是使用过GMS011芯片&#xff08;是国内24S&#xff09;下的公司出来的芯片&#xff0c;寄存器和管脚全对标。 在这里我就大概说一下芯片的说用方法 一、硬件设计 该芯片支持双通道射频直采 支持协议JESD204B 14位 采样…

leetcode二叉树(五)-二叉树层序遍历

题目 102.二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7…

【网络篇】计算机网络——网络层详述(笔记)

目录 一、网络层 1. 网络传输流程简述 2. 转发和路由选择 3. 控制平面&#xff1a;SDN 方法 二、路由器工作原理 1. 概述 &#xff08;1&#xff09;输入端口 &#xff08;2&#xff09;交换结构 &#xff08;3&#xff09;输出端口 &#xff08;4&#xff09;路由选…

CAS详谈---无锁的锁机制

假设有多个线程想要操作同一个资源对象&#xff0c;我们首先想到的是使用互斥锁&#xff0c;但是互斥锁是悲观的。 悲观&#xff0c;即操作系统会悲观的认为如果不严格同步线程调用&#xff0c;那么一定会产生异常&#xff0c;所以互斥锁会将资源锁定&#xff0c;只供一个线程…

【动手学深度学习】7.5 批量规范化(个人向笔记)

训练深层神经网络是十分困难的&#xff0c;特别是在较短的时间内使它们收敛更加棘手。而本节的批量规范化&#xff08;batch normalization&#xff09; 可以持续加速深层网络的收敛速度结合下节会介绍道德残差块&#xff0c;批量规范化使得研究人员能够训练100层以上的网络 1.…

nbsaas vue3管理后台框架

nbsaas vue3管理后台框架 一、项目概述 Nbsaas Admin Vue 是一个基于 Vue.js 3.0 构建的轻量级后台管理系统&#xff0c;结合了现代前端技术栈的最佳实践&#xff0c;旨在帮助开发者快速构建具有高可扩展性和良好用户体验的后台管理系统。该项目拥有简洁的 UI 设计&#xff0…

Hikyuu教程 | 滚动回测与滚动寻优系统

前面介绍了如何使用 hikyuu 进行策略回测参数优化&#xff0c;同时也提到了这种简单的参数优化本质其实是对历史数据的过拟合&#xff0c;通常并不具备直接使用的意义。那么有什么办法来减缓这种过拟合影响&#xff0c;让参数优化发挥实际的作用呢&#xff1f;答案是——使用滚…

源码编译方式安装htppd软件

一.源码编译安装httpd软件 1.安装阿帕奇的依赖&#xff0c;安装apr软件&#xff0c;阿帕奇正常运行的环境这个环境就是apr。 2.安装apr-util软件&#xff0c;主要提供针对apr环境的管理工具&#xff0c; 3.安装阿帕奇软件即httpd软件。 如上图所示&#xff0c;就是三个软件的…