2024年10月

直角平面
NumberPlane

Manim
库中用于创建二维坐标平面的对象,它可以帮助用户在场景中可视化坐标轴以及网格线。

通过坐标轴、网格线以及刻度,它能够动态地展示函数曲线、几何图形以及它们的变换过程,使得复杂的数学概念变得直观易懂。

NumberPlane
提供了
x轴

y轴
,通常是中心对称的,

默认情况下,
NumberPlane
会显示背景网格,这对于展示数学函数、几何形状等非常有帮助。

我们可以定义坐标平面的范围,为坐标轴添加标签,并将其他形状、函数或动画放置在
NumberPlane
上,从而在动画中展示数学概念。

下面将介绍
Manim
中的
NumberPlane
对象以及一些常用的使用示例。

1. 主要参数

NumberPlane
的主要参数有:

参数名称 类型 说明
x_range Sequence[float] 直角平面的
横坐标轴
范围,间隔
y_range Sequence[float] 直角平面的
纵坐标轴
范围,间隔
x_length float 直角平面宽度
y_length float 直角平面高度
background_line_style dict 直角平面背景网格线的样式
faded_line_style dict 淡化网格线的样式,用于辅助背景网格线
faded_line_ratio int 定义淡化网格线与背景网格线的比例
make_smooth_after_applying_functions bool 应用函数后是否进行平滑处理

直角平面中,常见的是坐标轴和背景网格线,其样式通过
background_line_style
参数来设置。

此外,
NumberPlane
中还有个
faded_line_style
参数,它用于定义淡化网格线的样式,

这些淡化网格线通常用于辅助背景网格线,以提供更细致的视觉效果或帮助区分不同的坐标区域。

一般情况下,不需要使用
faded_line_style

faded_line_ratio

2. 主要方法

Manim
中的坐标系统
CoordinateSystem
类提供了很多方法,包括:

  1. 获取和设置坐标系统属性的方法
  2. 坐标系中坐标和屏幕上坐标转换的方法
  3. 坐标系中绘制图形(点,线,面等等)的方法

后续介绍各种坐标系统时会用在示例中用到其中的方法,这里不再一一介绍。


NumberPlane
继承自
CoordinateSystem
类,所以可以直接使用它的各种方法。

3. 使用示例

下面通过几个示例展示
NumberPlane
的功能。

3.1. 基础二维坐标平面

默认情况下,
NumberPlane
显示坐标轴和背景网格线,帮助用户清晰地看到坐标系的划分。

坐标轴默认是白色的,背景网格线默认是蓝色的。

下面的示例展示一个标准的二维坐标平面,包含
x轴

y轴

x轴

y轴
的范围分别设置为
-7~7

-4~4
,网格线间隔为
1

plane = NumberPlane(
    x_range=[-7, 7],
    y_range=[-4, 4],
    x_length=6,
    y_length=4,
)

3.2. 自定义网格线样式

NumberPlane
的背景网格线和淡化网格线的样式都可以自定义,

下面的示例对
背景网格线

淡化网格线
的颜色、宽度和透明度进行了设置。

背景网格线是青色,淡化网格线是灰色。

plane = NumberPlane(
    x_range=[-7, 7],
    y_range=[-4, 4],
    x_length=6,
    y_length=4,
    background_line_style={
        "stroke_color": TEAL,
        "stroke_width": 4,
        "stroke_opacity": 0.6,
    },
    faded_line_style={
        "stroke_color": GREY,
        "stroke_opacity": 0.3,
    },
    faded_line_ratio=2,
)

注意,这里还设置了
faded_line_ratio=2
,意思是每1格
背景网格线
相当于2格的
淡化网格线

3.3. 不同比例的坐标轴

默认情况下,
NumberPlane

X轴

Y轴
的间隔是相等的,

实际上,通过设置不同的坐标范围和显示范围,可以创建不同比例的坐标轴。

也就是Y轴的间隔可以大于X轴的间隔,也可以小于X轴的间隔。

# Y轴的间隔大于X轴的间隔
plane1 = NumberPlane(
    x_range=[-7, 7],
    y_range=[-3, 3],
    x_length=3,
    y_length=3,
)

# Y轴的间隔小于X轴的间隔
plane2 = NumberPlane(
    x_range=[-3, 3],
    y_range=[-7, 7],
    x_length=3,
    y_length=3,
)

3.4. 绘制函数图像

使用
NumberPlane
最大的用途就在于可以在其中绘制函数图像,在坐标系中绘制图像或几何图形,

可以更方便的定位它们的位置,调整它们之间的变换关系。

下面的示例在坐标系中绘制了2个函数,使用了坐标系基类提供的绘图方法:
plot_parametric_curve

plane = NumberPlane(
    x_range=[-4, 4],
    y_range=[-16, 16],
    x_length=6,
    y_length=4,
)

g1 = plane.plot_parametric_curve(
    lambda x: [x, x**2],
    t_range=[-3.5, 3.5, 0.01],
)
g2 = plane.plot_parametric_curve(
    lambda x: [x, x**3],
    t_range=[-2.5, 2.5, 0.01],
)

4. 附件

文中的代码只是关键部分的截取,完整的代码共享在网盘中(
number_plane.py
),

下载地址:
完整代码
(访问密码: 6872)

InnoDB 的
MVCC(Multi-Version Concurrency Control,多版本并发控制)
是 MySQL 实现高并发事务处理的一种机制。通过 MVCC,InnoDB 可以在高并发环境下支持
事务隔离
,并提供
非阻塞的读操作
,从而避免锁定所有读操作带来的性能瓶颈。MVCC 允许事务在不加锁的情况下读取数据,保证了性能和一致性。

一、MVCC 基础概念

MVCC 的核心思想是
通过保存数据的多个版本
来支持并发读写。每个事务在读取数据时看到的是某个数据的特定版本,而不是当前最新的值。这个机制依赖于
Undo Log
,通过保存每一行记录的历史版本来支持多版本读取。(
undolog
)

  • 读写分离
    :MVCC 的主要特性是读取数据不阻塞写操作,写操作也不阻塞读取操作。实现了读写分离,提升了数据库并发处理能力。
  • 时间戳版本管理
    :每个事务都有一个唯一的
    事务 ID(Transaction ID,简称
    trx_id

    ,InnoDB 使用事务 ID 来区分数据的不同版本。

在这里插入图片描述

二、MVCC 读操作的两种类型

  1. 快照读(Snapshot Read)
    :读取的是历史版本的数据,是不加锁的普通读取操作。快照读是 MVCC 实现并发读的基础。
    • 比如
      SELECT * FROM table WHERE id = 1;
      ,执行的是不加锁的快照读操作。
  2. 当前读(Current Read)
    :读取的是最新版本的数据,并加锁保证数据的一致性和事务隔离。当前读通常伴随着写操作。
    • 比如
      SELECT * FROM table WHERE id = 1 FOR UPDATE;
      ,执行的是加锁的当前读操作。

三、MVCC 的工作原理

InnoDB 通过
行版本控制
来管理数据的不同版本。每一行数据在 InnoDB 的行记录中都包含两个隐藏的字段:

  • trx_id
    :表示最后一次对该行进行修改的事务 ID。
  • roll_pointer
    :指向该行之前的版本(即 Undo Log 中的记录),通过该指针可以找到数据的历史版本。

1. 事务的开始与结束

  • 每个事务在开始时会生成一个唯一的事务 ID
    trx_id
  • 在事务读取数据时,InnoDB 会根据 MVCC 机制判断该事务应该看到哪个版本的数据,主要依赖于事务的隔离级别(如
    Read Committed

    Repeatable Read
    )。

2. 数据的修改与版本管理

当一个事务修改数据时,InnoDB 会将当前数据的旧版本保存到
Undo Log
中,并在数据行中更新最新的
trx_id

roll_pointer
。这样即使数据被更新,其他未提交的事务依然可以通过 Undo Log 访问到旧版本的数据。

每行数据都会有多个历史版本,并通过
roll_pointer
链接到这些历史版本。

3. 版本链

版本链是基于
trx_id

roll_pointer
建立的,即每次修改数据时,会在 Undo Log 中存储旧版本,行记录会指向旧版本。版本链的结构如下:

最新记录 <-- trx_id_x --trx_id_y --trx_id_z... --> 历史版本

当事务读取数据时,会根据事务的隔离级别、当前事务的
trx_id
以及数据的
trx_id
来决定应该读取哪个版本。

四、MVCC 与事务隔离级别的关系

InnoDB 的 MVCC 机制在不同的事务隔离级别下有不同的行为表现:

  1. Read Committed
    (读取已提交):事务只会看到其他事务已经提交的版本,每次查询都会读取最新的已提交数据。这种隔离级别下,事务间存在不可重复读问题。
    • 每次查询时,InnoDB 都会返回当前已经提交的最新版本数据。
  2. Repeatable Read
    (可重复读,MySQL 默认隔离级别):事务在整个过程中看到的是同一个版本快照的数据,即便有其他事务提交了新的数据,当前事务依然看到自己开始时的一致性快照。
    • 事务开始时,InnoDB 会为事务创建一个
      一致性视图(consistent view)
      ,之后的所有查询都会返回该视图中的数据,保证了可重复读。

隔离级别与 MVCC 读方式的关系

  • Read Committed
    :每次读取都基于当前已经提交的最新版本,因此快照读是每次返回最新的数据。
  • Repeatable Read
    :快照读总是基于事务启动时的版本快照,因此即便其他事务提交了新的数据,当前事务依然看到的是旧数据,直到它自己提交。

五、MVCC 的实现细节

1. 事务 ID 与版本控制

InnoDB 使用事务 ID(
trx_id
)来区分不同事务对数据的修改。每次修改都会将当前事务的事务 ID 记录在该行数据的
trx_id
字段中。通过事务 ID,InnoDB 可以判断当前事务是否能够看到某一行数据的特定版本。

2. 一致性视图

一致性视图(Consistent Read View)是指当前事务只能看到事务启动时的数据快照,除非事务使用的是当前读。InnoDB 会根据当前事务的事务 ID 和一致性视图来确定读取哪一个版本的数据。

  • 对于快照读
    :根据事务的开始时间戳、其他事务的提交情况,来判断该事务看到的是哪个版本的数据。
  • 对于当前读
    :必须是最新的数据,InnoDB 会为当前事务加锁,防止其他事务修改或读取。

3. Undo Log 与 Rollback Segment

当一个事务修改数据时,InnoDB 会将原始的数据版本存入
Undo Log
中,Undo Log 中记录了数据的历史版本。通过
roll_pointer
,InnoDB 可以在发生并发时,允许其他事务读取数据的旧版本。

六、MVCC 的实际应用场景

1. 电商系统的订单查询

在电商系统中,用户在事务 A 中查询订单时,订单数据可能会被其他事务修改或插入新记录。通过 MVCC,事务 A 查询时看到的订单数据是一致的,即便其他事务插入了新的订单,事务 A 依然能看到一个稳定的订单视图。

2. 金融系统的账户余额查询

在金融系统中,用户账户余额的查询需要确保每次查询得到的结果一致,即使账户余额在后台因某些事务而发生变化,用户也能看到一贯的账户信息,保证用户体验的一致性。MVCC 在这种场景下能保证高并发下的数据一致性。

七、总结

  • MVCC 是 InnoDB 实现高并发事务处理的重要机制
    ,通过维护多版本数据和不加锁的快照读,解决了传统数据库加锁所带来的并发性能问题。
  • MVCC 结合事务的隔离级别,保证数据的
    一致性

    隔离性
    ,使得 MySQL 在高并发场景下依然能保持较高的性能。
  • 通过事务 ID、Undo Log、一致性视图等机制,InnoDB 能够高效地管理事务间的数据访问,从而避免常见的并发读写问题,例如
    幻读

    不可重复读
    等。

前言

C#、.NET、.NET Core、WPF、WinForm、Unity等相关技术的学习、工作路线集合(持续更新)!!!

C#学习路线

学习C#优先推荐先看微软官方文档,对于C#/.NET的学习,微软官方文档是一个非常好的学习资料。这些文档提供了详细的说明和示例代码,涵盖了C#/.NET的各个方面。按照文档的目录逐步学习,可以让你循序渐进地掌握语言和框架的各个语法和概念。

C#语言详细介绍

  • C# 语言是适用于 .NET 平台(免费的跨平台开源开发环境)的最流行语言。 C# 程序可以在许多不同的设备上运行,从物联网 (IoT) 设备到云以及介于两者之间的任何设备。 可为手机、台式机、笔记本电脑和服务器编写应用。

C#学习路线图

WPF学习路线

WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Windows 应用。它提供了灵活的布局、数据绑定、样式和模板、动画效果等功能,让开发者可以创建出吸引人且交互性强的应用程序。

WinForm学习路线

WinForm是一个传统的桌面应用程序框架,它基于 Windows 操作系统的原生控件和窗体。通过简单易用的 API,开发者可以快速构建基于窗体的应用程序,并且可以利用多种控件和事件来实现应用程序的功能和交互。

.NET/.NET Core学习路线

合理利用微软官方技术文档

对于C#/.NET的学习,微软官方文档是一个非常好的学习资料。这些文档提供了详细的说明和示例代码,涵盖了C#/.NET的各个方面。按照文档的目录逐步学习,可以让你循序渐进地掌握语言和框架的各个语法和概念。

ASP.NET Core开发者指南

2024 年 .NET 开发人员路线图

Java 开发者学习 C# 的路线

因为还处于数据分析的学习阶段(野生Python学者),所以在kaggle这个网站找了两个数据集来给自己练练手。

准备工作

import pandas as pd
import os
import matplotlib.pyplot as plt
import numpy as np
from random import choice

获取数据

这里我下载了两个数据集第一个是关于咖啡的销售情况,第二个是关于Instagram这个网站1000名最受欢迎的博主的数据。
我就从咖啡的销售情况这个表入手,因为我看了第二个表实在是没有什么眉目去做T.T
数据集文件放在最下方有需要的可以自行下载。

# 读取目录内的文件
directory = r'C:\Users\Admin\Desktop\demo\练习'
files = os.listdir(directory)
print(files)
['coffee_result.csv', 'Instagram-Data.csv']
# 存放文件
files_list = []
for file in files:
    if file.endswith('.csv'):
        directory_file = fr'{directory}\{file}'
        files_list.append(directory_file)
print(files_list)
['C:\\Users\\Admin\\Desktop\\demo\\练习\\coffee_result.csv', 'C:\\Users\\Admin\\Desktop\\demo\\练习\\Instagram-Data.csv']
# 读取需要的文件
df = pd.read_csv(files_list[0])

查看一些必要信息

df.info()
df
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1464 entries, 0 to 1463
Data columns (total 6 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   date         1464 non-null   object 
 1   datetime     1464 non-null   object 
 2   cash_type    1464 non-null   object 
 3   card         1375 non-null   object 
 4   money        1464 non-null   float64
 5   coffee_name  1464 non-null   object 
dtypes: float64(1), object(5)
memory usage: 68.8+ KB
date datetime cash_type card money coffee_name
0 2024-03-01 2024-03-01 10:15:50.520 card ANON-0000-0000-0001 38.70 Latte
1 2024-03-01 2024-03-01 12:19:22.539 card ANON-0000-0000-0002 38.70 Hot Chocolate
2 2024-03-01 2024-03-01 12:20:18.089 card ANON-0000-0000-0002 38.70 Hot Chocolate
3 2024-03-01 2024-03-01 13:46:33.006 card ANON-0000-0000-0003 28.90 Americano
4 2024-03-01 2024-03-01 13:48:14.626 card ANON-0000-0000-0004 38.70 Latte
... ... ... ... ... ... ...
1459 2024-09-05 2024-09-05 20:30:14.964 card ANON-0000-0000-0587 32.82 Cappuccino
1460 2024-09-05 2024-09-05 20:54:24.429 card ANON-0000-0000-0588 23.02 Americano
1461 2024-09-05 2024-09-05 20:55:31.429 card ANON-0000-0000-0588 32.82 Cappuccino
1462 2024-09-05 2024-09-05 21:26:28.836 card ANON-0000-0000-0040 27.92 Americano with Milk
1463 2024-09-05 2024-09-05 21:27:29.969 card ANON-0000-0000-0040 27.92 Americano with Milk

1464 rows × 6 columns

print(df['cash_type'].unique().tolist(),'\n', 
len(df['card'].unique().tolist()),'\n', 
df['coffee_name'].unique().tolist(),'\n',
len(df['coffee_name'].unique().tolist()))
['card', 'cash'] 
 589 
 ['Latte', 'Hot Chocolate', 'Americano', 'Americano with Milk', 'Cocoa', 'Cortado', 'Espresso', 'Cappuccino'] 
 8

通过info返回的信息可以看到card列存在一些空值,那我就把空值处理一下

df[df['card'].isnull()]
date datetime cash_type card money coffee_name
12 2024-03-02 2024-03-02 10:30:35.668 cash NaN 40.0 Latte
18 2024-03-03 2024-03-03 10:10:43.981 cash NaN 40.0 Latte
41 2024-03-06 2024-03-06 12:30:27.089 cash NaN 35.0 Americano with Milk
46 2024-03-07 2024-03-07 10:08:58.945 cash NaN 40.0 Latte
49 2024-03-07 2024-03-07 11:25:43.977 cash NaN 40.0 Latte
... ... ... ... ... ... ...
657 2024-05-31 2024-05-31 09:23:58.791 cash NaN 39.0 Latte
677 2024-06-01 2024-06-01 20:54:59.267 cash NaN 39.0 Cocoa
685 2024-06-02 2024-06-02 22:43:10.636 cash NaN 34.0 Americano with Milk
691 2024-06-03 2024-06-03 21:42:51.734 cash NaN 34.0 Americano with Milk
692 2024-06-03 2024-06-03 21:43:37.471 cash NaN 34.0 Americano with Milk

89 rows × 6 columns

空值是由支付类型为现金支付的那一列对应的行产生的

df['card'] = df['card'].fillna("-1")
df['card'].isnull().any()
np.False_

对数据进行处理

在info返回的信息看到date这一列的数值类型是对象,我就把它变成日期类型方便我自己后续操作

print(type(df.loc[1,'date']),type(df.loc[1,'datetime']))
df.loc[1,'date']
<class 'str'> <class 'str'>
'2024-03-01'
# 调整日期格式提取每行数据的月份
df['date'] = pd.to_datetime(df['date'])
df['datetime'] = pd.to_datetime(df['datetime'])
df['month'] = df['date'].dt.month
print(len(df['month'].unique()))
7

查看每月的销售情况

因为9月份的数据只有5天所以这个月就不纳入分析

# 查看每月的销量以及金额
df_six = df[df['month']!=9].copy()
month = df_six['month'].unique()    # 把月份单独拎出
month_sales = df_six.groupby('month')['money'].count()
month_sum = df_six.groupby('month')['money'].sum()

figure,axes = plt.subplots(1,2,figsize=[16,8])
figure.suptitle("Month sales and sum",size=20)
ax1 = axes[0].bar(month,month_sales)
axes[0].set_xlabel('Month',size=16)
axes[0].set_ylabel('Count',size=16)

ax2 = axes[1].bar(month,month_sum)
axes[1].set_xlabel('Month',size=16)
axes[1].set_ylabel('Sum',size=16)

axes[0].bar_label(ax1,fmt="%d",label_type="center")
axes[1].bar_label(ax2,fmt="%d",label_type="center")
plt.subplots_adjust(wspace=0.5)

image

统计每款咖啡的营销情况

每款咖啡每月的营销额

nrows,ncols = 2,4
figure3,axes = plt.subplots(nrows,ncols,figsize=[16,8],sharex=True,sharey=True)

coffee_month_sales = df_six.groupby(['month','coffee_name'])['money'].sum().reset_index(name='sum')
coffee_names = coffee_month_sales['coffee_name'].unique().tolist()

for idx,coffee_name in enumerate(coffee_names):
    x,y = divmod(idx,ncols)
    coffee_data = coffee_month_sales[coffee_month_sales['coffee_name']==coffee_name]
    bars = axes[x,y].bar(coffee_data['month'],coffee_data['sum'])
    axes[x,y].bar_label(bars,fmt="%d",label_type="center")
    subtitle = f"{coffee_name} {int(coffee_data['sum'].sum())}"
    axes[x,y].set_title(subtitle)
    axes[x,y].set_xlabel('month',size=16)
    axes[x,y].set_ylabel('sum',size=16)
    
figure3.suptitle('coffee month sales',size=20)
plt.tight_layout()
plt.subplots_adjust(wspace=0.5)

image

查看不同咖啡的受众人数以及占比

stati = df_six.groupby('coffee_name')['money'].count().reset_index(name='buyers')
stati.sort_values(by='buyers',ascending=True,inplace=True,ignore_index=True)

figure2,axes = plt.subplots(1,2,figsize=(16,8))
figure2.suptitle("Coffee audience number and proportion",size=20)
ax1 = axes[0].barh(stati.iloc[:,0],stati.iloc[:,1])
axes[0].bar_label(ax1,fmt="%d",label_type="center")
axes[0].set_ylabel("Kind",size=16)
axes[0].set_xlabel("Sum",size=16)

axes[1].pie(stati.iloc[:,1],labels=stati.iloc[:,0],autopct='%0.1f')
plt.subplots_adjust(wspace=0.5)

image

统计客户的实际消费情况

cardholder = df_six[df_six['card']!='-1'].copy()
cardholder['tag'] = 1
cardholder.drop(columns=['date','datetime','cash_type'],inplace=True)
cardholder['month_sum'] = cardholder.groupby('card')['tag'].transform('sum')
active_buyer = cardholder.groupby('card')['month_sum'].max().reset_index(name='buys')
active_buyer.sort_values(by='buys',inplace=True,ignore_index=True,ascending=False)

cardholder['money_sum'] = cardholder.groupby('card')['money'].transform('sum')
money_sum = cardholder.drop_duplicates(subset='card',ignore_index=True).copy()
money_sum.drop(columns=['money','coffee_name','month','tag','month_sum'],inplace=True)
money_sum.sort_values(by='money_sum',inplace=True,ignore_index=True,ascending=False)
result = pd.merge(active_buyer,money_sum)
print('总消费金额平均数:',result['money_sum'].mean(),'\n',
      result.head(10))
总消费金额平均数: 75.29034111310592 
                   card  buys  money_sum
0  ANON-0000-0000-0012    96    2772.44
1  ANON-0000-0000-0009    67    2343.98
2  ANON-0000-0000-0141    44    1101.08
3  ANON-0000-0000-0097    38    1189.34
4  ANON-0000-0000-0040    30     910.12
5  ANON-0000-0000-0003    27     744.04
6  ANON-0000-0000-0001    17     646.14
7  ANON-0000-0000-0134    13     470.76
8  ANON-0000-0000-0024    12     422.26
9  ANON-0000-0000-0059    12     337.00

通过打印的数据可以看到这算是最活跃的一批用户了

程度大致就做到这种情况了,谢谢观看,如果有什么好的方法也可以在评论区评论!

本文的代码以及思路根据以下链接做过参考:
[
https://tianchi.aliyun.com/notebook/464175?spm=a2c22.21852664.0.0.260f379cEBLg8w
]

数据集下载链接:
https://storage.googleapis.com/kagglesdsdata/datasets/5328600/9620471/index.csv?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcp-kaggle-com%40kaggle-161607.iam.gserviceaccount.com%2F20241019%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20241019T074949Z&X-Goog-Expires=259200&X-Goog-SignedHeaders=host&X-Goog-Signature=b136263c03183c66881a72c1df4dcc4a2a1523e5ece138f7d060a718d58823e983c90e018217c2557ea03fb1a3a75e8436055e3f86b1b97a684e6a74699d74de6bebfccaa2e27bc9193b9eda676e236c848ad62085afc36d408755a06f8062bdd88d20cb42da5fa397b785a39ff7b0b4a01465a0de0c348d4c2ef0a3aa9be989e8e2a897963172ec51bbff461277ede44290b60f5942988fd64801b089d7557e648a4e740fe9d5a64f9c27877a36e25a1cc0e4f4a61e5f8caf6325e5bff8dc8f5a0496464dbc7cc0c257610940deb59c864b368e9eb32fed796fb12f790d5197fa255bdf8bf96a06bb3c0b9540eebafaa438874a026f1ae86e045a7cc44e60c0

强化学习笔记之【DDPG算法】


前言:

本文为强化学习笔记第二篇,第一篇讲的是Q-learning和DQN

就是因为DDPG引入了Actor-Critic模型,所以比DQN多了两个网络,网络名字功能变了一下,其它的就是软更新之类的小改动而已

本文初编辑于2024.10.6

CSDN主页:
https://blog.csdn.net/rvdgdsva

博客园主页:
https://www.cnblogs.com/hassle

博客园本文链接:

真 · 图文无关


原论文伪代码

  • 上述代码为DDPG原论文中的伪代码


需要先看:

Deep Reinforcement Learning (DRL) 算法在 PyTorch 中的实现与应用
【DDPG部分】【没有在选择一个新的动作的时候,给policy函数返回的动作值增加一个噪音】【critic网络与下面不同】

深度强化学习笔记——DDPG原理及实现(pytorch)
【DDPG伪代码部分】【这个跟上面的一样没有加噪音】【critic网络与上面不同】

【深度强化学习】(4) Actor-Critic 模型解析,附Pytorch完整代码
【选看】【Actor-Critic理论部分】


如果需要给policy函数返回的动作值增加一个噪音,实现如下

def select_action(self, state, noise_std=0.1):
    state = torch.FloatTensor(state.reshape(1, -1))
    action = self.actor(state).cpu().data.numpy().flatten()
    
    # 添加噪音,上面两个文档的代码都没有这个步骤
    noise = np.random.normal(0, noise_std, size=action.shape)
    action = action + noise
    
    return action


DDPG 中的四个网络

注意!!!这个图只展示了Critic网络的更新,没有展示Actor网络的更新

  • Actor 网络(策略网络)

    • 作用
      :决定给定状态 ss 时,应该采取的动作 a=π(s)a=π(s),目标是找到最大化未来回报的策略。
    • 更新
      :基于 Critic 网络提供的 Q 值更新,以最大化 Critic 估计的 Q 值。
  • Target Actor 网络(目标策略网络)

    • 作用
      :为 Critic 网络提供更新目标,目的是让目标 Q 值的更新更为稳定。
    • 更新
      :使用软更新,缓慢向 Actor 网络靠近。
  • Critic 网络(Q 网络)

    • 作用
      :估计当前状态 ss 和动作 aa 的 Q 值,即 Q(s,a)Q(s,a),为 Actor 提供优化目标。
    • 更新
      :通过最小化与目标 Q 值的均方误差进行更新。
  • Target Critic 网络(目标 Q 网络)

    • 作用
      :生成 Q 值更新的目标,使得 Q 值更新更为稳定,减少振荡。
    • 更新
      :使用软更新,缓慢向 Critic 网络靠近。

大白话解释:

​ 1、DDPG实例化为actor,输入state输出action
​ 2、DDPG实例化为actor_target
​ 3、DDPG实例化为critic_target,输入next_state和actor_target(next_state)经DQN计算输出target_Q
​ 4、DDPG实例化为critic,输入state和action输出current_Q,输入state和actor(state)【这个参数需要注意,不是action】经负均值计算输出actor_loss

​ 5、current_Q 和target_Q进行critic的参数更新
​ 6、actor_loss进行actor的参数更新

action实际上是batch_action,state实际上是batch_state,而
batch_action != actor(batch_state)

因为actor是频繁更新的,而采样是随机采样,不是所有batch_action都能随着actor的更新而同步更新

Critic网络的更新是一发而动全身的,相比于Actor网络的更新要复杂要重要许多


代码核心更新公式

\[target\underline{~}Q = critic\underline{~}target(next\underline{~}state, actor\underline{~}target(next\underline{~}state))
\\target\underline{~}Q = reward + (1 - done) \times gamma \times target\underline{~}Q.detach()
\]

  • 上述代码与伪代码对应,意为计算预测Q值

\[critic\underline{~}loss = MSELoss(critic(state, action), target\underline{~}Q)
\\critic\underline{~}optimizer.zero\underline{~}grad()
\\critic\underline{~}loss.backward()
\\critic\underline{~}optimizer.step()
\]

  • 上述代码与伪代码对应,意为使用均方误差损失函数更新Critic

\[actor\underline{~}loss = -critic(state,actor(state)).mean()
\\actor\underline{~}optimizer.zero\underline{~}grad()
\\ actor\underline{~}loss.backward()
\\ actor\underline{~}optimizer.step()
\]

  • 上述代码与伪代码对应,意为使用确定性策略梯度更新Actor

\[critic\underline{~}target.parameters().data=(tau \times critic.parameters().data + (1 - tau) \times critic\underline{~}target.parameters().data)
\\
actor\underline{~}target.parameters().data=(tau \times actor.parameters().data + (1 - tau) \times actor\underline{~}target.parameters().data)
\]

  • 上述代码与伪代码对应,意为使用策略梯度更新目标网络


Actor和Critic的角色

  • Actor
    :负责选择动作。它根据当前的状态输出一个确定性动作。
  • Critic
    :评估Actor的动作。它通过计算状态-动作值函数(Q值)来评估给定状态和动作的价值。

更新逻辑

  • Critic的更新

    1. 使用经验回放缓冲区(Experience Replay)从中采样一批经验(状态、动作、奖励、下一个状态)。
    2. 计算目标Q值:使用目标网络(critic_target)来估计下一个状态的Q值(target_Q),并结合当前的奖励。
    3. 使用均方误差损失函数(MSELoss)来更新Critic的参数,使得预测的Q值(target_Q)与当前Q值(current_Q)尽量接近。
  • Actor的更新

    1. 根据当前的状态(state)从Critic得到Q值的梯度(即对Q值相对于动作的偏导数)。
    2. 使用确定性策略梯度(DPG)的方法来更新Actor的参数,目标是最大化Critic评估的Q值。


个人理解:

DQN算法是将q_network中的参数每n轮一次复制到target_network里面

DDPG使用系数
\(\tau\)
来更新参数,将学习到的参数更加soft地拷贝给目标网络

DDPG采用了actor-critic网络,所以比DQN多了两个网络