学习日志23

# QML as Q Learning function approximator
# Need to specify STATE input format
# Computational Basis Encoding
# action output is still softmax [a_0, a_1, a_2, a_3, a_4, a_5]
# Deep Q-Learning DQN
# Experimence Replay (For i.i.d sampling) 
# Target Network (Updata every C episodes) ==> Another Circuit Parameter Set

# This version is enhanced with PyTorch
# Adapt some code from 
# PyTorch tutorial on deep reinforcement learning
# and
# Xanadu AI github repository
# Environment: OpenAI gym FrozenLake

# 状态输入编码: 使用计算基编码将状态输入编码为量子态。
# 动作输出: 使用softmax函数输出动作概率分布。
# 深度Q学习(DQN): 使用深度神经网络作为Q函数的近似器。
# 经验回放: 使用经验回放缓存来进行无关样本的训练。
# 目标网络: 使用另一组独立的参数作为目标网络,定期更新以提高稳定性。
# PyTorch集成: 利用PyTorch的深度学习框架实现。
##
import pennylane as qml

from pennylane import numpy as np
from pennylane.optimize import NesterovMomentumOptimizer

import torch
import torch.nn as nn 
from torch.autograd import Variable

import matplotlib.pyplot as plt
from datetime import datetime
import pickle

import gym
import time
import random
from collections import namedtuple
from copy import deepcopy

from gym.envs.registration import register
register(
    id='Deterministic-ShortestPath-4x4-FrozenLake-v0', # name given to this new environment
    entry_point='ShortestPathFrozenLake:ShortestPathFrozenLake', # env entry point
    kwargs={'map_name': '4x4', 'is_slippery': False} # argument passed to the env
)
# register(
#     id='Deterministic-4x4-FrozenLake-v0', # name given to this new environment
#     entry_point='gym.envs.toy_text.frozen_lake:FrozenLakeEnv', # env entry point
#     kwargs={'map_name': '4x4', 'is_slippery': False} # argument passed to the env
# )


## Definition of Replay Memory
## If next_state == None
## it is in the terminal state



Transition = namedtuple('Transition',
						('state', 'action', 'reward', 'next_state', 'done'))


class ReplayMemory(object):

	def __init__(self, capacity):
		self.capacity = capacity
		self.memory = []
		self.position = 0

	def push(self, *args):
		"""Saves a transition."""
		if len(self.memory) < self.capacity:
			self.memory.append(None)
		self.memory[self.position] = Transition(*args)
		self.position = (self.position + 1) % self.capacity

	def sample(self, batch_size):
		return random.sample(self.memory, batch_size)

	def output_all(self):
		return self.memory

	def __len__(self):
		return len(self.memory)
####


## Plotting Function ##
"""
Note: the plotting code is origin from Yang, Chao-Han Huck, et al. "Enhanced Adversarial Strategically-Timed Attacks Against Deep Reinforcement Learning." 
## ICASSP 2020-2020 IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP). IEEE, 2020.
If you use the code in your research, please cite the original reference. 
"""

def plotTrainingResultCombined(_iter_index, _iter_reward, _iter_total_steps, _fileTitle):
	fig, ax = plt.subplots()
	# plt.yscale('log')
	ax.plot(_iter_index, _iter_reward, '-b', label='Reward')
	ax.plot(_iter_index, _iter_total_steps, '-r', label='Total Steps')
	leg = ax.legend();

	ax.set(xlabel='Iteration Index', 
		   title=_fileTitle)
	fig.savefig(_fileTitle + "_"+ datetime.now().strftime("NO%Y%m%d%H%M%S") + ".png")

# 这段代码定义了一个名为 plotTrainingResultCombined 的函数,用于绘制训练过程中的奖励和总步数的图表。以下是对该函数的详细解释:
#
# 导入必要的库:
#
# matplotlib.pyplot 用于绘制图表。
# 定义函数 plotTrainingResultCombined,接受以下参数:
#
# _iter_index: 训练迭代的索引列表。
# _iter_reward: 每次迭代的奖励值列表。
# _iter_total_steps: 每次迭代的总步数列表。
# _fileTitle: 图表的标题。
# 创建一个新的图形和坐标轴对象。
#
# 使用 ax.plot() 在同一个图表上绘制奖励和总步数曲线,分别使用蓝色和红色线条。
#
# 添加图例 ax.legend()。
#
# 设置 x 轴标签为 "Iteration Index",并使用 _fileTitle 作为图表标题。
#
# 使用 fig.savefig() 将图表保存为一个 PNG 文件,文件名包含当前日期和时间。
#
# 这个函数可以用于可视化训练过程中的奖励和总步数的变化情况,帮助分析模型的性能和收敛情况。通过将奖励和总步数曲线绘制在同一个图表上,可以更好地理解它们之间的关系。
def plotTrainingResultReward(_iter_index, _iter_reward, _iter_total_steps, _fileTitle):
	fig, ax = plt.subplots()
	# plt.yscale('log')
	ax.plot(_iter_index, _iter_reward, '-b', label='Reward')
	# ax.plot(_iter_index, _iter_total_steps, '-r', label='Total Steps')
	leg = ax.legend();

	ax.set(xlabel='Iteration Index', 
		   title=_fileTitle)
	fig.savefig(_fileTitle + "_REWARD" + "_"+ datetime.now().strftime("NO%Y%m%d%H%M%S") + ".png")

# 这段代码定义了一个名为 plotTrainingResultReward 的函数,用于绘制训练过程中的奖励曲线。以下是对该函数的详细解释:
#
# 导入必要的库:
#
# matplotlib.pyplot 用于绘制图表。
# 定义函数 plotTrainingResultReward,接受以下参数:
#
# _iter_index: 训练迭代的索引列表。
# _iter_reward: 每次迭代的奖励值列表。
# _iter_total_steps: 每次迭代的总步数列表。
# _fileTitle: 图表的标题。
# 创建一个新的图形和坐标轴对象。
#
# 使用 ax.plot() 绘制奖励曲线,使用蓝色线条。注意,这个函数只绘制奖励曲线,而不绘制总步数曲线。
#
# 添加图例 ax.legend()。
#
# 设置 x 轴标签为 "Iteration Index",并使用 _fileTitle 作为图表标题。
#
# 使用 fig.savefig() 将图表保存为一个 PNG 文件,文件名包含当前日期和时间,并添加 "_REWARD" 后缀。
#
# 这个函数可以用于可视化训练过程中的奖励变化情况,帮助分析模型的性能和收敛情况。通过只绘制奖励曲线,可以更清晰地观察奖励的变化趋势,而不会被总步数曲线干扰。这对于理解模型的学习过程很有帮助。
##############################################################################################

def decimalToBinaryFixLength(_length, _decimal):

	if isinstance(_decimal, tuple):

		decimal_value = _decimal[0]

		binNum = bin(int(decimal_value))[2:]
	else:

		binNum = bin(int(_decimal))[2:]


	outputNum = [int(item) for item in binNum]
	if len(outputNum) < _length:
		outputNum = np.concatenate((np.zeros((_length-len(outputNum),)),np.array(outputNum)))
	else:
		outputNum = np.array(outputNum)
	return outputNum
# 这个函数 decimalToBinaryFixLength 将一个十进制数转换为固定长度的二进制数组。以下是对该函数的详细解释:

# 函数接受两个参数:

# _length: 输出二进制数组的固定长度。
# _decimal: 要转换的十进制数。它可以是一个单独的数值,也可以是一个包含数值的元组。
# 首先检查 _decimal 是否为元组。如果是,则取元组的第一个元素作为十进制数进行转换。
#
# 使用 bin() 函数将十进制数转换为二进制字符串,并去掉前两个字符 "0b"。
#
# 将二进制字符串转换为整数列表 outputNum。
#
# 检查 outputNum 的长度是否小于 _length。如果是,则使用 np.concatenate() 在数组前面添加足够数量的 0,以达到 _length 的长度。
#
# 如果 outputNum 的长度大于或等于 _length,则直接返回 outputNum。
#
# 这个函数的主要目的是将一个十进制数转换为固定长度的二进制数组。这在一些机器学习和信号处理的应用中很有用,例如在神经网络的输入或输出中使用固定长度的二进制编码。通过确保输出数组的长度固定,可以确保数据格式的一致性,并简化后续的数据处理和分析。

## PennyLane Part ##

# Specify the datatype of the Totch tensor
dtype = torch.DoubleTensor

## Define a FOUR qubit system
dev = qml.device('default.qubit', wires=4)
# dev = qml.device('qiskit.basicaer', wires=4)
def statepreparation(a):

	"""Quantum circuit to encode a the input vector into variational params

	Args:
		a: feature vector of rad and rad_square => np.array([rad_X_0, rad_X_1, rad_square_X_0, rad_square_X_1])
	"""
	
	# Rot to computational basis encoding
	# a = [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8]
	# 这段注释解释了
	# statepreparation
	# 函数的作用和输入参数。
	#
	# 函数说明:
	#
	# 这个量子电路的目的是将输入向量编码到变分参数中。
	# 输入参数
	# a:
	#
	# a
	# 是一个特征向量, 包含
	# rad_X_0, rad_X_1, rad_square_X_0
	# 和
	# rad_square_X_1
	# 这四个元素。
	# 这些元素可能代表某些物理量的弧度值和平方弧度值。
	# 编码到计算基底:
	#
	# 注释中提到
	# a = [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8], 这表示输入向量可能有更多的元素。
	# 这个注释暗示, 在实际实现中, 可能需要将更长的输入向量编码到四个量子比特的状态中。
	# 总之, 这个函数的作用是将输入特征向量编码到量子态中, 为后续的量子电路计算做准备。这种编码是变分量子电路中的一个重要步骤, 可以将经典数据转换为量子状态, 以便在量子硬件上进行计算和优化。

	for ind in range(len(a)):
		qml.RX(np.pi * a[ind], wires=ind)
		qml.RZ(np.pi * a[ind], wires=ind)


def layer(W):
	""" Single layer of the variational classifier.

	Args:
		W (array[float]): 2-d array of variables for one layer
	"""

	qml.CNOT(wires=[0, 1])
	qml.CNOT(wires=[1, 2])
	qml.CNOT(wires=[2, 3])


	qml.Rot(W[0, 0], W[0, 1], W[0, 2], wires=0)
	qml.Rot(W[1, 0], W[1, 1], W[1, 2], wires=1)
	qml.Rot(W[2, 0], W[2, 1], W[2, 2], wires=2)
	qml.Rot(W[3, 0], W[3, 1], W[3, 2], wires=3)

	# 这个
	# layer
	# 函数定义了变分分类器的单个层。以下是对该函数的详细解释:
	#
	# 函数说明:
	#
	# 这个函数定义了变分分类器的单个层。
	# 输入参数
	# W
	# 是一个二维数组, 包含了该层的变分参数。
	# 量子门操作:
	# 首先, 该函数应用了三个CNOT门, 分别连接量子比特
	# 0 - 1、1 - 2和2 - 3。这些CNOT门用于在量子比特之间创建纠缠。
	# 然后, 该函数对每个量子比特应用了一个
	# Rot门。Rot门是一个由三个旋转角度参数化的复合门, 分别对应
	# X、Y和Z轴的旋转。这些旋转角度由输入矩阵W的元素决定。
	# 通过这些操作, layer函数定义了一个单层的变分量子电路。这个电路包含了量子比特之间的纠缠, 以及可调节的单比特旋转。这种结构可以用于构建更复杂的变分量子分类器, 通过优化W矩阵中的参数来学习分类任务。

	# 这个函数是变分量子电路的核心部分, 体现了量子机器学习中常见的电路设计模式, 即利用可调节的量子门来编码问题的特征, 并通过优化这些参数来学习目标函数。
	

@qml.qnode(dev, interface='torch')

#
# 这段代码使用PennyLane库定义了一个量子电路。以下是对代码的详细解释:
#
# Torch张量的数据类型:代码将Torch张量的数据类型指定为torch.DoubleTensor。
#
# 定义一个四量子比特系统:代码使用qml.device函数创建了一个具有四个量子比特的量子设备。在这里,使用了默认的量子比特设备,但代码中也给出了使用Qiskit后端的替代选项。
#
# 状态准备函数:statepreparation函数接受输入向量a,并将其编码到四量子比特系统的量子态中。该函数对每个量子比特应用一系列旋转门(RX和RZ门),旋转角度由输入向量a的元素决定。
#
# 层函数:layer函数定义了变分分类器的单个层。它首先应用一系列CNOT门来纠缠量子比特,然后对每个量子比特应用Rot门。Rot门的参数由输入矩阵W的元素决定。
#
# 量子电路函数:@qml.qnode(dev, interface='torch')装饰器定义了一个量子电路函数,该函数以输入向量a和参数矩阵W为输入,并返回量子电路的输出。该电路在量子设备dev上执行,接口设置为'torch',这允许将该电路与PyTorch集成。
#
# 这段代码提供了一个变分量子电路的基本结构,其中输入数据被编码到量子态中,电路参数被优化以执行分类任务。具体的应用和模型训练将取决于所解决的问题以及项目的整体背景。
def circuit(weights, angles=None):
	"""The circuit of the variational classifier."""
	# Can consider different expectation value
	# PauliX , PauliY , PauliZ , Identity  

	statepreparation(angles)
	
	for W in weights:
		layer(W)

	return [qml.expval(qml.PauliZ(ind)) for ind in range(4)]
# 这个 circuit 函数定义了整个变分量子分类器的电路。以下是对该函数的详细解释:
#
# 函数说明:

# 这个函数定义了变分量子分类器的整个电路。
# 输入参数:
#
# weights: 一个包含多个 2D 数组的列表,每个 2D 数组代表一层的变分参数。
# angles: 一个包含输入特征的数组,用于在状态准备阶段编码到量子态中。
# 电路操作:
#
# 首先,该函数调用 statepreparation 函数,将输入特征 angles 编码到量子态中。
# 然后,该函数遍历 weights 列表中的每个 2D 数组,并对每个数组调用 layer 函数,以应用单个变分层。
# 最后,该函数计算每个量子比特上的 Z 方向期望值,并将结果作为列表返回。
# 期望值计算:
#
# 该函数使用 qml.expval(qml.PauliZ(ind)) 来计算每个量子比特上的 Z 方向期望值。这些期望值可以用作变分量子分类器的输出。
# 总之,这个 circuit 函数将整个变分量子分类器的电路组合在一起,包括状态准备、多个变分层,以及最终的期望值计算。这个函数为训练和使用变分量子分类器提供了一个高级接口。根据具体的应用需求,可以考虑计算其他类型的期望值,如 PauliX、PauliY 或 Identity。

def variational_classifier(var_Q_circuit, var_Q_bias , angles=None):
	"""The variational classifier."""

	# Change to SoftMax???

	weights = var_Q_circuit
	# bias_1 = var_Q_bias[0]
	# bias_2 = var_Q_bias[1]
	# bias_3 = var_Q_bias[2]
	# bias_4 = var_Q_bias[3]
	# bias_5 = var_Q_bias[4]
	# bias_6 = var_Q_bias[5]

	# raw_output = circuit(weights, angles=angles) + np.array([bias_1,bias_2,bias_3,bias_4,bias_5,bias_6])
	#raw_output = circuit(weights, angles=angles) + torch.tensor(var_Q_bias)
	#raw_output = circuit(weights, angles=angles) + var_Q_bias
	raw_output = torch.stack(circuit(weights, angles=angles)) + var_Q_bias
	# We are approximating Q Value
	# Maybe softmax is no need
	# softMaxOutPut = np.exp(raw_output) / np.exp(raw_output).sum()

	return raw_output
# 这个 variational_classifier 函数定义了整个变分量子分类器的输出。以下是对该函数的详细解释:
#
# 函数说明:
#
# 这个函数定义了变分量子分类器的输出。
# 输入参数:
#
# var_Q_circuit: 一个包含多个 2D 数组的列表,每个 2D 数组代表一层的变分参数。
# var_Q_bias: 一个包含多个偏置值的张量或数组。
# angles: 一个包含输入特征的数组,用于在状态准备阶段编码到量子态中。
# 输出计算:
#
# 首先,该函数从 var_Q_circuit 中获取权重参数。
# 然后,该函数调用 circuit 函数,传入权重参数和输入特征 angles。circuit 函数返回一个包含四个期望值的列表。
# 接下来,该函数将 circuit 函数的输出与 var_Q_bias 相加,得到最终的原始输出。
# 最后,该函数返回原始输出。
# 注释:
#
# 注释中提到了一些其他的输出计算方式,如使用 SoftMax 函数。但在当前实现中,这些方式都被注释掉了。
# 注释还提到,这个函数可能在近似 Q 值,因此可能不需要使用 SoftMax 函数。
# 总之,这个 variational_classifier 函数将 circuit 函数的输出与偏置值相加,得到最终的分类器输出。根据具体的应用需求,可以考虑使用其他的输出计算方式,如 SoftMax 函数。

def square_loss(labels, predictions):
	""" Square loss function

	Args:
		labels (array[float]): 1-d array of labels
		predictions (array[float]): 1-d array of predictions
	Returns:
		float: square loss
	"""
	loss = 0
	for l, p in zip(labels, predictions):
	    loss = loss + (l - p) ** 2
	loss = loss / len(labels)
	# print("LOSS")

	# print(loss)

	# output = torch.abs(predictions - labels)**2
	# output = torch.sum(output) / len(labels)

	# loss = nn.MSELoss()
	# output = loss(labels.double(), predictions.double())

	return loss
# 这个 square_loss 函数定义了变分量子分类器的平方损失函数。以下是对该函数的详细解释:
#
# 函数说明:
#
# 这个函数计算给定标签和预测之间的平方损失。
# 输入参数:
#
# labels: 一个包含标签的 1D 数组。
# predictions: 一个包含预测值的 1D 数组。
# 损失计算:
#
# 该函数遍历标签和预测值的配对,并计算每个配对的平方损失。
# 然后,该函数将所有平方损失相加,并除以标签的长度,得到平均平方损失。
# 注释:
#
# 注释中提供了其他计算平方损失的方式,包括使用 PyTorch 的 torch.abs() 和 nn.MSELoss() 函数。
# 这些注释表明,该函数可以使用不同的实现方式来计算平方损失。
# 总之,这个 square_loss 函数实现了一个简单的平方损失计算。它接受标签和预测值作为输入,并返回它们之间的平均平方损失。这个损失函数可以用于优化变分量子分类器的参数,以最小化预测误差。根据具体的应用需求,也可以考虑使用其他类型的损失函数,如交叉熵损失或 Huber 损失。
# def square_loss(labels, predictions):
# 	""" Square loss function

# 	Args:
# 		labels (array[float]): 1-d array of labels
# 		predictions (array[float]): 1-d array of predictions
# 	Returns:
# 		float: square loss
# 	"""
# 	# In Deep Q Learning
# 	# labels = target_action_value_Q
# 	# predictions = action_value_Q

# 	# loss = 0
# 	# for l, p in zip(labels, predictions):
# 	# 	loss = loss + (l - p) ** 2
# 	# loss = loss / len(labels)

# 	# loss = nn.MSELoss()
# 	output = torch.abs(predictions - labels)**2
# 	output = torch.sum(output) / len(labels)
# 	# output = loss(torch.tensor(predictions), torch.tensor(labels))
# 	# print("LOSS OUTPUT")
# 	# print(output)

# 	return output

def abs_loss(labels, predictions):
	""" Square loss function

	Args:
		labels (array[float]): 1-d array of labels
		predictions (array[float]): 1-d array of predictions
	Returns:
		float: square loss
	"""
	# In Deep Q Learning
	# labels = target_action_value_Q
	# predictions = action_value_Q

	# loss = 0
	# for l, p in zip(labels, predictions):
	# 	loss = loss + (l - p) ** 2
	# loss = loss / len(labels)

	# loss = nn.MSELoss()
	output = torch.abs(predictions - labels)
	output = torch.sum(output) / len(labels)
	# output = loss(torch.tensor(predictions), torch.tensor(labels))
	# print("LOSS OUTPUT")
	# print(output)

	return output

def huber_loss(labels, predictions):
	""" Square loss function

	Args:
		labels (array[float]): 1-d array of labels
		predictions (array[float]): 1-d array of predictions
	Returns:
		float: square loss
	"""
	# In Deep Q Learning
	# labels = target_action_value_Q
	# predictions = action_value_Q

	# loss = 0
	# for l, p in zip(labels, predictions):
	# 	loss = loss + (l - p) ** 2
	# loss = loss / len(labels)

	# loss = nn.MSELoss()
	loss = nn.SmoothL1Loss()
	# output = loss(torch.tensor(predictions), torch.tensor(labels))
	# print("LOSS OUTPUT")
	# print(output)

	return loss(labels, predictions)


def cost(var_Q_circuit, var_Q_bias, features, labels):
	"""Cost (error) function to be minimized."""

	# predictions = [variational_classifier(weights, angles=f) for f in features]
	# Torch data type??
	
	predictions = [variational_classifier(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, angles=decimalToBinaryFixLength(4,item.state))[item.action] for item in features]
	# predictions = torch.tensor(predictions,requires_grad=True)
	# labels = torch.tensor(labels)
	# print("PRIDICTIONS:")
	# print(predictions)
	# print("LABELS:")
	# print(labels)

	return square_loss(labels, predictions)
#
# 这个 cost 函数定义了变分量子分类器的成本(错误)函数,需要最小化该函数。以下是对该函数的详细解释:
#
# 函数说明:
#
# 这个函数计算变分量子分类器的成本函数,即预测值与标签之间的平方损失。
# 输入参数:
#
# var_Q_circuit: 一个包含多个 2D 数组的列表,每个 2D 数组代表一层的变分参数。
# var_Q_bias: 一个包含多个偏置值的张量或数组。
# features: 一个包含输入特征的列表。每个特征都是一个对象,包含 state 和 action 属性。
# labels: 一个包含标签的列表。
# 预测计算:
#
# 该函数使用 variational_classifier 函数对每个输入特征进行预测,得到一个预测值列表。
# 在进行预测时,该函数将输入特征 features 中的 state 属性转换为二进制格式,并传给 variational_classifier 函数。
# 损失计算:
#
# 该函数使用 square_loss 函数计算预测值与标签之间的平方损失。
# 注释:
#
# 注释中提到了使用 PyTorch 张量进行数据类型转换的方式,但在当前实现中这些代码被注释掉了。
# 总之,这个 cost 函数定义了变分量子分类器的成本函数,即预测值与标签之间的平方损失。该函数接受变分参数、输入特征和标签作为输入,并返回需要最小化的成本值。在实际应用中,可以使用优化算法来最小化这个成本函数,从而训练出性能优秀的变分量子分类器。
#############################

def epsilon_greedy(var_Q_circuit, var_Q_bias, epsilon, n_actions, s, train=False):
	"""
	@param Q Q values state x action -> value
	@param epsilon for exploration
	@param s number of states
	@param train if true then no random actions selected
	"""

	# Modify to incorporate with Variational Quantum Classifier
	# epsilon should change along training
	# In the beginning => More Exploration
	# In the end => More Exploitation

	# More Random
	#np.random.seed(int(datetime.now().strftime("%S%f")))


	if train or np.random.rand() < ((epsilon/n_actions)+(1-epsilon)):
		# action = np.argmax(Q[s, :])
		# variational classifier output is torch tensor
		# action = np.argmax(variational_classifier(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, angles = decimalToBinaryFixLength(9,s)))
		action = torch.argmax(variational_classifier(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, angles = decimalToBinaryFixLength(4,s)))
	else:
		# need to be torch tensor
		action = torch.tensor(np.random.randint(0, n_actions))
	return action
#
# 这个 epsilon_greedy 函数实现了一个 epsilon-greedy 的探索策略,用于在变分量子分类器的训练和使用过程中进行动作选择。以下是对该函数的详细解释:
#
# 函数说明:
#
# 这个函数根据当前状态和探索概率 epsilon 选择一个动作。
# 输入参数:
#
# var_Q_circuit: 一个包含多个 2D 数组的列表,每个 2D 数组代表一层的变分参数。
# var_Q_bias: 一个包含多个偏置值的张量或数组。
# epsilon: 探索概率,用于控制探索和利用之间的平衡。
# n_actions: 可选动作的数量。
# s: 当前状态。
# train: 一个布尔值,指示是否处于训练模式。
# 动作选择:
#
# 如果处于训练模式或随机数小于 (epsilon/n_actions) + (1-epsilon)(即以 epsilon 概率进行探索),则选择使用变分量子分类器预测得到的动作。
# 否则,随机选择一个动作。
# 注释:
#
# 注释中提到,在训练的早期阶段,应该更多地进行探索,而在后期阶段,应该更多地进行利用。
# 注释还提到,可以使用当前时间作为随机种子,以获得更随机的动作选择。
# 总之,这个 epsilon_greedy 函数实现了一个常见的探索策略,用于在训练和使用变分量子分类器时进行动作选择。它根据当前的探索概率 epsilon 和预测输出,在探索和利用之间进行平衡。这种策略有助于在训练初期进行充分的探索,并在后期逐步向利用转移,从而提高变分量子分类器的性能。

def deep_Q_Learning(alpha, gamma, epsilon, episodes, max_steps, n_tests, render = False, test=False):
	"""
	@param alpha learning rate
	@param gamma decay factor
	@param epsilon for exploration
	@param max_steps for max step in each episode
	@param n_tests number of test episodes
	"""

	
	env = gym.make('Deterministic-ShortestPath-4x4-FrozenLake-v0')
	# env = gym.make('Deterministic-4x4-FrozenLake-v0')
	n_states, n_actions = env.observation_space.n, env.action_space.n
	print("NUMBER OF STATES:" + str(n_states))
	print("NUMBER OF ACTIONS:" + str(n_actions))

	# Initialize Q function approximator variational quantum circuit
	# initialize weight layers

	num_qubits = 4
	num_layers = 2
	# var_init = (0.01 * np.random.randn(num_layers, num_qubits, 3), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

	var_init_circuit = Variable(torch.tensor(0.01 * np.random.randn(num_layers, num_qubits, 3), device='cpu').type(dtype), requires_grad=True)
	var_init_bias = Variable(torch.tensor([0.0, 0.0, 0.0, 0.0], device='cpu').type(dtype), requires_grad=True)

	# Define the two Q value function initial parameters
	# Use np copy() function to DEEP COPY the numpy array
	var_Q_circuit = var_init_circuit
	var_Q_bias = var_init_bias
	# print("INIT PARAMS")
	# print(var_Q_circuit)

	var_target_Q_circuit = var_Q_circuit.clone().detach()
	var_target_Q_bias = var_Q_bias.clone().detach()

	##########################
	# Optimization method => random select train batch from replay memory
	# and opt

	# opt = NesterovMomentumOptimizer(0.01)

	# opt = torch.optim.Adam([var_Q_circuit, var_Q_bias], lr = 0.1)
	# opt = torch.optim.SGD([var_Q_circuit, var_Q_bias], lr=0.1, momentum=0.9)
	opt = torch.optim.RMSprop([var_Q_circuit, var_Q_bias], lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)

	## NEed to move out of the function
	TARGET_UPDATE = 20
	batch_size = 5
	OPTIMIZE_STEPS = 5
	##


	target_update_counter = 0

	iter_index = []
	iter_reward = []
	iter_total_steps = []

	cost_list = []


	timestep_reward = []


	# Demo of generating a ACTION
	# Output a numpy array of value for each action

	# Define the replay memory
	# Each transition:
	# (s_t_0, a_t_0, r_t, s_t_1, 'DONE')

	memory = ReplayMemory(80)

	# Input Angle = decimalToBinaryFixLength(9, stateInd)
	# Input Angle is a numpy array

	# stateVector = decimalToBinaryFixLength(9, stateInd)

	# q_val_s_t = variational_classifier(var_Q, angles=stateVector)
	# # action_t = q_val_s_t.argmax()
	# action_t = epsilon_greedy(var_Q, epsilon, n_actions, s)
	# q_val_target_s_t = variational_classifier(var_target_Q, angles=stateVector)

	# train the variational classifier


	for episode in range(episodes):
		print(f"Episode: {episode}")
		# Output a s in decimal format
		s = env.reset()
		# Doing epsilog greedy action selection
		# With var_Q
		a = epsilon_greedy(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, epsilon = epsilon, n_actions = n_actions, s = s).item()
		t = 0
		total_reward = 0
		done = False


		while t < max_steps:
			if render:
				print("###RENDER###")
				env.render()
				print("###RENDER###")
			t += 1

			target_update_counter += 1

			# Execute the action 
			s_, reward, done, info, extra_value = env.step(a)
			# print("Reward : " + str(reward))
			# print("Done : " + str(done))
			total_reward += reward
			# a_ = np.argmax(Q[s_, :])
			a_ = epsilon_greedy(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, epsilon = epsilon, n_actions = n_actions, s = s_).item()
			
			# print("ACTION:")
			# print(a_)

			memory.push(s, a, reward, s_, done)

			if len(memory) > batch_size:

				# Sampling Mini_Batch from Replay Memory

				batch_sampled = memory.sample(batch_size = batch_size)

				# Transition = (s_t, a_t, r_t, s_t+1, done(True / False))

				# item.state => state
				# item.action => action taken at state s
				# item.reward => reward given based on (s,a)
				# item.next_state => state arrived based on (s,a)

				Q_target = [item.reward + (1 - int(item.done)) * gamma * torch.max(variational_classifier(var_Q_circuit = var_target_Q_circuit, var_Q_bias = var_target_Q_bias, angles=decimalToBinaryFixLength(4,item.next_state))) for item in batch_sampled]
				# Q_prediction = [variational_classifier(var_Q, angles=decimalToBinaryFixLength(9,item.state))[item.action] for item in batch_sampled ]

				# Gradient Descent
				# cost(weights, features, labels)
				# square_loss_training = square_loss(labels = Q_target, Q_predictions)
				# print("UPDATING PARAMS...")

				# CHANGE TO TORCH OPTIMIZER
				
				# var_Q = opt.step(lambda v: cost(v, batch_sampled, Q_target), var_Q)
				# opt.zero_grad()
				# loss = cost(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, features = batch_sampled, labels = Q_target)
				# print(loss)
				# FIX this gradient error
				# loss.backward()
				# opt.step(loss)

				def closure():
					opt.zero_grad()
					loss = cost(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, features = batch_sampled, labels = Q_target)
					# print(loss)
					loss.backward()
					return loss
				opt.step(closure)

				# print("UPDATING PARAMS COMPLETED")
				current_replay_memory = memory.output_all()
				current_target_for_replay_memory = [item.reward + (1 - int(item.done)) * gamma * torch.max(variational_classifier(var_Q_circuit = var_target_Q_circuit, var_Q_bias = var_target_Q_bias, angles=decimalToBinaryFixLength(4,item.next_state))) for item in current_replay_memory]
				# current_target_for_replay_memory = [item.reward + (1 - int(item.done)) * gamma * np.max(variational_classifier(var_target_Q, angles=decimalToBinaryFixLength(9,item.next_state))) for item in current_replay_memory]

				# if t%5 == 0:
				# 	cost_ = cost(var_Q_circuit = var_Q_circuit, var_Q_bias = var_Q_bias, features = current_replay_memory, labels = current_target_for_replay_memory)
				# 	print("Cost: ")
				# 	print(cost_.item())
				# 	cost_list.append(cost_)


			if target_update_counter > TARGET_UPDATE:
				print("UPDATEING TARGET CIRCUIT...")

				var_target_Q_circuit = var_Q_circuit.clone().detach()
				var_target_Q_bias = var_Q_bias.clone().detach()
				
				target_update_counter = 0

			s, a = s_, a_

			if done:
				if render:
					print("###FINAL RENDER###")
					env.render()
					print("###FINAL RENDER###")
					print(f"This episode took {t} timesteps and reward: {total_reward}")
				epsilon = epsilon / ((episode/100) + 1)
				# print("Q Circuit Params:")
				# print(var_Q_circuit)
				print(f"This episode took {t} timesteps and reward: {total_reward}")
				timestep_reward.append(total_reward)
				iter_index.append(episode)
				iter_reward.append(total_reward)
				iter_total_steps.append(t)
				break
	# if render:
	# 	print(f"Here are the Q values:\n{Q}\nTesting now:")
	# if test:
	# 	test_agent(Q, env, n_tests, n_actions)
	return timestep_reward, iter_index, iter_reward, iter_total_steps, var_Q_circuit, var_Q_bias
# 这个 deep_Q_Learning 函数实现了一个基于变分量子分类器的深度 Q 学习算法。以下是对该函数的详细解释:
#
# 函数说明:
#
# 这个函数使用深度 Q 学习算法训练一个变分量子分类器,用于解决 OpenAI Gym 中的 Deterministic-ShortestPath-4x4-FrozenLake-v0 环境。
# 输入参数:
#
# alpha: 学习率。
# gamma: 衰减因子。
# epsilon: 探索概率。
# episodes: 训练的总轮数。
# max_steps: 每个回合的最大步数。
# n_tests: 测试回合的数量。
# render: 是否渲染环境。
# test: 是否进行测试。
# 算法实现:
#
# 初始化变分量子分类器的参数,包括权重层和偏置。
# 定义优化器为 RMSProp。
# 定义回放内存,用于存储之前的转移。
# 在每个训练回合中:
# 使用 epsilon-greedy 策略选择动作。
# 执行动作,获得奖励和下一状态。
# 将转移存入回放内存。
# 如果回放内存中有足够的样本,则从中采样一个小批量,计算目标 Q 值,并使用梯度下降更新变分量子分类器的参数。
# 每隔一定步数,将目标网络的参数更新为当前网络的参数。
# 记录每个回合的总奖励和总步数,并在训练结束时返回这些指标。
# 注释:
#
# 注释中提到了一些优化器的选择,包括 Nesterov Momentum 和 Adam。
# 注释还提到了一些超参数,如目标网络更新频率和小批量大小。
# 总之,这个 deep_Q_Learning 函数实现了一个基于变分量子分类器的深度 Q 学习算法,用于解决 OpenAI Gym 中的 Deterministic-ShortestPath-4x4-FrozenLake-v0 环境。它通过使用回放内存和目标网络更新等技术,有效地训练出了一个性能良好的变分量子分类器。该函数可以作为一个基准,用于进一步研究和优化基于变分量子分类器的强化学习算法。

# def test_agent(Q, env, n_tests, n_actions, delay=1):
# 	for test in range(n_tests):
# 		print(f"Test #{test}")
# 		s = env.reset()
# 		done = False
# 		epsilon = 0
# 		while True:
# 			time.sleep(delay)
# 			env.render()
# 			a = epsilon_greedy(Q, epsilon, n_actions, s, train=True)
# 			print(f"Chose action {a} for state {s}")
# 			s, reward, done, info = env.step(a)
# 			if done:
# 				if reward > 0:
# 					print("Reached goal!")
# 				else:
# 					print("Shit! dead x_x")
# 				time.sleep(3)
# 				break

# Should add plotting function and KeyboardInterrupt Handler

if __name__ =="__main__":
	alpha = 0.4
	gamma = 0.999
	epsilon = 1.
	episodes = 10
	max_steps = 2500
	n_tests = 2
	timestep_reward, iter_index, iter_reward, iter_total_steps , var_Q_circuit, var_Q_bias = deep_Q_Learning(alpha, gamma, epsilon, episodes, max_steps, n_tests, test = False)
	
	print(timestep_reward)
	

	## Drawing Training Result ##
	file_title = 'VQDQN_Frozen_Lake_NonSlip_Dynamic_Epsilon_RMSProp' + datetime.now().strftime("NO%Y%m%d%H%M%S")
	
	plotTrainingResultReward(_iter_index = iter_index, _iter_reward = iter_reward, _iter_total_steps = iter_total_steps, _fileTitle = 'Quantum_DQN_Frozen_Lake_NonSlip_Dynamic_Epsilon_RMSProp')

	## Saving the model
	with open(file_title + "_var_Q_circuit" + ".txt", "wb") as fp:
			pickle.dump(var_Q_circuit, fp)

	with open(file_title + "_var_Q_bias" + ".txt", "wb") as fp:
			pickle.dump(var_Q_bias, fp)

	with open(file_title + "_iter_reward" + ".txt", "wb") as fp:
			pickle.dump(iter_reward, fp)



#
# 这段代码是一个使用深度 Q 学习 (Deep Q-Learning) 算法训练一个强化学习模型的示例。以下是对代码的解释:
#
# 如果当前脚本是主程序入口 (if __name__ == "__main__":),则执行以下操作:
#
# 设置超参数:
#
# alpha: 学习率
# gamma: 折扣因子
# epsilon: 探索概率
# episodes: 训练的总轮数
# max_steps: 每轮训练的最大步数
# n_tests: 每轮训练后进行的测试次数
# 调用 deep_Q_Learning() 函数进行训练,并获取以下输出:
#
# timestep_reward: 每个时间步的奖励
# iter_index: 每轮训练的索引
# iter_reward: 每轮训练的总奖励
# iter_total_steps: 每轮训练的总步数
# var_Q_circuit: 训练得到的 Q 网络的权重
# var_Q_bias: 训练得到的 Q 网络的偏置
# 打印 timestep_reward。
#
# 绘制训练结果:
#
# 使用 plotTrainingResultReward() 函数绘制训练过程中的奖励和总步数变化曲线。
# 文件名包含当前时间戳,以便区分不同的训练结果。
# 保存训练得到的模型参数:
#
# 将 var_Q_circuit 和 var_Q_bias 分别保存到文件中。
# 将 iter_reward 保存到文件中。
# 总的来说,这段代码实现了一个深度 Q 学习算法的训练过程,并保存了训练结果供后续使用。






这段代码是一个深度Q学习(Deep Q-Learning, DQN)的实现,它使用变分量子电路作为Q函数的近似器。代码的目的是训练一个智能体在OpenAI Gym中的“Deterministic-ShortestPath-4x4-FrozenLake-v0”环境中导航,以找到从起点到终点的最短路径。以下是代码的主要组成部分和流程:

导入必要的库:包括PennyLane(用于量子电路模拟)、torch(PyTorch深度学习框架)、gym(OpenAI Gym环境)等。

环境设置:注册并初始化FrozenLake环境,设置环境的观察空间和动作空间。

定义回放记忆(Replay Memory):用于存储智能体的状态转移,以便进行经验回放。

定义绘图函数:用于可视化训练过程中的奖励和总步数。

十进制转二进制函数:将状态转换为固定长度的二进制数组,以便于量子电路处理。

定义量子电路:包括状态准备和变分量子层,使用PennyLane库构建。

变分量子分类器:构建量子电路并输出Q值,用于后续的Q学习和动作选择。

损失函数:定义平方损失函数,用于训练过程中的误差计算。

成本函数:结合变分量子分类器的输出和实际奖励,计算需要最小化的成本。

探索策略:实现epsilon-greedy策略,平衡探索和利用。

深度Q学习主函数:整合上述组件,执行训练过程,包括初始化参数、选择动作、执行环境交互、更新回放记忆、梯度下降优化等。

训练和测试:设置超参数,调用deep_Q_Learning函数进行训练,并绘制训练结果,保存模型参数。

主程序:当脚本作为主程序运行时,执行训练过程,并进行结果可视化和模型保存。

整体上,这段代码展示了如何将量子计算与深度强化学习结合起来,通过变分量子电路来近似Q函数,并在经典环境中进行训练和测试。这种方法可能为解决一些传统方法难以处理的复杂问题提供了新的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值