문서 | 텐서드 | 기능 | 예, 튜토리얼 및 데모 | 인용 | 설치 | 질문 | 기여
Torchrl은 Pytorch를위한 오픈 소스 강화 학습 (RL) 라이브러리입니다.
라이브러리에 대한보다 선별 된 설명은 전체 용지를 읽으십시오.
라이브러리의 기본 기능으로 신속하게 증가하는 시작 자습서를 확인하십시오!
Torchrl 문서는 여기에서 찾을 수 있습니다. 자습서와 API 참조가 포함되어 있습니다.
Torchrl은 또한 코드를 디버깅하거나 단순히 RL의 기본 사항을 배우는 데 도움이되는 RL 지식 기반을 제공합니다. 여기에서 확인하십시오.
도서관을 더 잘 알 수있는 소개 비디오가 있습니다.
Torchrl은 도메인-공수성이므로 여러 분야에서 사용할 수 있습니다. 몇 가지 예는 다음과 같습니다.
TensorDict
사용하여 단순하고 휴대용 RL 코드베이스를 작성합니다 RL 알고리즘은 매우 이질적이며 설정에서 코드베이스를 재활용하기가 어려울 수 있습니다 (예 : 온라인에서 오프라인, 상태 기반에서 픽셀 기반 학습에 이르기까지). Torchrl은 자신의 RL 코드베이스를 간소화하는 데 사용할 수있는 편리한 데이터 구조 (1) 인 TensorDict
통해이 문제를 해결합니다. 이 도구를 사용하면 100 줄 미만의 코드로 전체 PPO 교육 스크립트를 작성할 수 있습니다!
import torch
from tensordict . nn import TensorDictModule
from tensordict . nn . distributions import NormalParamExtractor
from torch import nn
from torchrl . collectors import SyncDataCollector
from torchrl . data . replay_buffers import TensorDictReplayBuffer ,
LazyTensorStorage , SamplerWithoutReplacement
from torchrl . envs . libs . gym import GymEnv
from torchrl . modules import ProbabilisticActor , ValueOperator , TanhNormal
from torchrl . objectives import ClipPPOLoss
from torchrl . objectives . value import GAE
env = GymEnv ( "Pendulum-v1" )
model = TensorDictModule (
nn . Sequential (
nn . Linear ( 3 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 2 ),
NormalParamExtractor ()
),
in_keys = [ "observation" ],
out_keys = [ "loc" , "scale" ]
)
critic = ValueOperator (
nn . Sequential (
nn . Linear ( 3 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 128 ), nn . Tanh (),
nn . Linear ( 128 , 1 ),
),
in_keys = [ "observation" ],
)
actor = ProbabilisticActor (
model ,
in_keys = [ "loc" , "scale" ],
distribution_class = TanhNormal ,
distribution_kwargs = { "low" : - 1.0 , "high" : 1.0 },
return_log_prob = True
)
buffer = TensorDictReplayBuffer (
storage = LazyTensorStorage ( 1000 ),
sampler = SamplerWithoutReplacement (),
batch_size = 50 ,
)
collector = SyncDataCollector (
env ,
actor ,
frames_per_batch = 1000 ,
total_frames = 1_000_000 ,
)
loss_fn = ClipPPOLoss ( actor , critic )
adv_fn = GAE ( value_network = critic , average_gae = True , gamma = 0.99 , lmbda = 0.95 )
optim = torch . optim . Adam ( loss_fn . parameters (), lr = 2e-4 )
for data in collector : # collect data
for epoch in range ( 10 ):
adv_fn ( data ) # compute advantage
buffer . extend ( data )
for sample in buffer : # consume data
loss_vals = loss_fn ( sample )
loss_val = sum (
value for key , value in loss_vals . items () if
key . startswith ( "loss" )
)
loss_val . backward ()
optim . step ()
optim . zero_grad ()
print ( f"avg reward: { data [ 'next' , 'reward' ]. mean (). item (): 4.4f } " )
다음은 Environment API가 롤아웃 실행 중에 한 기능에서 다른 기능으로 데이터를 전달하기 위해 Tensordict에 의존하는 방법의 예입니다.
TensorDict
하면 환경, 모델 및 알고리즘에서 코드를 쉽게 재사용 할 수 있습니다.
예를 들어 Torchrl에서 롤아웃을 코딩하는 방법은 다음과 같습니다.
- obs, done = env.reset()
+ tensordict = env.reset()
policy = SafeModule(
model,
in_keys=["observation_pixels", "observation_vector"],
out_keys=["action"],
)
out = []
for i in range(n_steps):
- action, log_prob = policy(obs)
- next_obs, reward, done, info = env.step(action)
- out.append((obs, next_obs, action, log_prob, reward, done))
- obs = next_obs
+ tensordict = policy(tensordict)
+ tensordict = env.step(tensordict)
+ out.append(tensordict)
+ tensordict = step_mdp(tensordict) # renames next_observation_* keys to observation_*
- obs, next_obs, action, log_prob, reward, done = [torch.stack(vals, 0) for vals in zip(*out)]
+ out = torch.stack(out, 0) # TensorDict supports multiple tensor operations
이를 사용하여 Torchrl은 모듈, ENV, 수집기, 재생 버퍼 및 라이브러리 손실의 입력 / 출력 서명을 추상화하여 모든 프리미티브가 설정을 통해 쉽게 재활용 할 수 있도록합니다.
다음은 Torchrl의 오프 정책 교육 루프의 또 다른 예입니다 (데이터 수집기, 재생 버퍼, 손실 및 최적화기가 인스턴스화되었다고 가정).
- for i, (obs, next_obs, action, hidden_state, reward, done) in enumerate(collector):
+ for i, tensordict in enumerate(collector):
- replay_buffer.add((obs, next_obs, action, log_prob, reward, done))
+ replay_buffer.add(tensordict)
for j in range(num_optim_steps):
- obs, next_obs, action, hidden_state, reward, done = replay_buffer.sample(batch_size)
- loss = loss_fn(obs, next_obs, action, hidden_state, reward, done)
+ tensordict = replay_buffer.sample(batch_size)
+ loss = loss_fn(tensordict)
loss.backward()
optim.step()
optim.zero_grad()
이 교육 루프는 데이터 구조에 대한 최소한의 가정을 만들어 알고리즘에서 재사용 할 수 있습니다.
Tensordict는 장치 및 모양의 여러 텐서 작업을 지원합니다 (Tensordict의 모양 또는 배치 크기는 포함 된 모든 텐서의 일반적인 임의의 N 첫 번째 치수) :
# stack and cat
tensordict = torch . stack ( list_of_tensordicts , 0 )
tensordict = torch . cat ( list_of_tensordicts , 0 )
# reshape
tensordict = tensordict . view ( - 1 )
tensordict = tensordict . permute ( 0 , 2 , 1 )
tensordict = tensordict . unsqueeze ( - 1 )
tensordict = tensordict . squeeze ( - 1 )
# indexing
tensordict = tensordict [: 2 ]
tensordict [:, 2 ] = sub_tensordict
# device and memory location
tensordict . cuda ()
tensordict . to ( "cuda:1" )
tensordict . share_memory_ ()
Tensordict에는 모델을 작성하는 데 필요한 모든 것을 포함하는 전용 tensordict.nn
모듈이 제공됩니다. 그리고 그것은 functorch
와 torch.compile
호환입니다!
transformer_model = nn.Transformer(nhead=16, num_encoder_layers=12)
+ td_module = SafeModule(transformer_model, in_keys=["src", "tgt"], out_keys=["out"])
src = torch.rand((10, 32, 512))
tgt = torch.rand((20, 32, 512))
+ tensordict = TensorDict({"src": src, "tgt": tgt}, batch_size=[20, 32])
- out = transformer_model(src, tgt)
+ td_module(tensordict)
+ out = tensordict["out"]
TensorDictSequential
클래스는 nn.Module
인스턴스의 시퀀스를 매우 모듈 식 방식으로 분기 할 수 있습니다. 예를 들어, 다음은 인코더 및 디코더 블록을 사용하여 변압기의 구현입니다.
encoder_module = TransformerEncoder (...)
encoder = TensorDictSequential ( encoder_module , in_keys = [ "src" , "src_mask" ], out_keys = [ "memory" ])
decoder_module = TransformerDecoder (...)
decoder = TensorDictModule ( decoder_module , in_keys = [ "tgt" , "memory" ], out_keys = [ "output" ])
transformer = TensorDictSequential ( encoder , decoder )
assert transformer . in_keys == [ "src" , "src_mask" , "tgt" ]
assert transformer . out_keys == [ "memory" , "output" ]
TensorDictSequential
원하는 입력 / 출력 키 세트를 쿼리하여 서브 그래프를 분리 할 수 있습니다.
transformer . select_subsequence ( out_keys = [ "memory" ]) # returns the encoder
transformer . select_subsequence ( in_keys = [ "tgt" , "memory" ]) # returns the decoder
자세한 내용은 긴장된 튜토리얼을 확인하십시오!
공통 라이브러리 (OpenAi Gym, Deepmind Control Lab 등) (1) 및 상태가없는 실행 (예 : 모델 기반 환경)을 지원하는 환경을위한 일반적인 인터페이스. 배치 된 환경 컨테이너는 병렬 실행을 허용합니다 (2) . 일반적인 pytorch-first 클래스의 텐서-특성화 클래스도 제공됩니다. Torchrl의 환경 API는 간단하지만 엄격하고 구체적입니다. 자세한 내용은 문서와 튜토리얼을 확인하십시오!
env_make = lambda : GymEnv ( "Pendulum-v1" , from_pixels = True )
env_parallel = ParallelEnv ( 4 , env_make ) # creates 4 envs in parallel
tensordict = env_parallel . rollout ( max_steps = 20 , policy = None ) # random rollout (no policy given)
assert tensordict . shape == [ 4 , 20 ] # 4 envs, 20 steps rollout
env_parallel . action_spec . is_in ( tensordict [ "action" ]) # spec check returns True
다중 프로세스 및 분산 데이터 수집기 (2) 동기식 또는 비동기 적으로 작동합니다. Tensordict를 사용하여 Torchrl의 교육 루프는 감독 학습에서 정기 교육 루프와 매우 유사하게 만들어집니다 (데이터 수집기 읽기-날짜가 수정 됨).
env_make = lambda : GymEnv ( "Pendulum-v1" , from_pixels = True )
collector = MultiaSyncDataCollector (
[ env_make , env_make ],
policy = policy ,
devices = [ "cuda:0" , "cuda:0" ],
total_frames = 10000 ,
frames_per_batch = 50 ,
...
)
for i , tensordict_data in enumerate ( collector ):
loss = loss_module ( tensordict_data )
loss . backward ()
optim . step ()
optim . zero_grad ()
collector . update_policy_weights_ ()
Torchrl을 사용한 초고속 데이터 수집에 대한 자세한 내용은 분산 수집기 예제를 확인하십시오.
효율적인 (2) 및 일반 (1) 모듈화 스토리지와 함께 버퍼 :
storage = LazyMemmapStorage ( # memory-mapped (physical) storage
cfg . buffer_size ,
scratch_dir = "/tmp/"
)
buffer = TensorDictPrioritizedReplayBuffer (
alpha = 0.7 ,
beta = 0.5 ,
collate_fn = lambda x : x ,
pin_memory = device != torch . device ( "cpu" ),
prefetch = 10 , # multi-threaded sampling
storage = storage
)
재생 버퍼는 오프라인 RL 의 일반적인 데이터 세트 주변의 랩퍼로도 제공됩니다.
from torchrl . data . replay_buffers import SamplerWithoutReplacement
from torchrl . data . datasets . d4rl import D4RLExperienceReplay
data = D4RLExperienceReplay (
"maze2d-open-v0" ,
split_trajs = True ,
batch_size = 128 ,
sampler = SamplerWithoutReplacement ( drop_last = True ),
)
for sample in data : # or alternatively sample = data.sample()
fun ( sample )
크로스 라이브러리 환경은 (1) , 장치 및 벡터 화 방식 (2) 으로 실행되며, 이는 에이전트가 사용할 환경에서 나오는 데이터를 처리하고 준비하는 다음과 같습니다.
env_make = lambda : GymEnv ( "Pendulum-v1" , from_pixels = True )
env_base = ParallelEnv ( 4 , env_make , device = "cuda:0" ) # creates 4 envs in parallel
env = TransformedEnv (
env_base ,
Compose (
ToTensorImage (),
ObservationNorm ( loc = 0.5 , scale = 1.0 )), # executes the transforms once and on device
)
tensordict = env . reset ()
assert tensordict . device == torch . device ( "cuda:0" )
기타 변환으로는 보상 스케일링 ( RewardScaling
), 모양 조작 (텐서 연결, 소수의 불완전 등), 연속 조작 ( CatFrames
), 크기 조정 ( Resize
) 등이 있습니다.
다른 라이브러리와 달리 변환은 목록으로 쌓여 있으며 (서로 래핑하지 않음), 마음대로 추가하고 제거 할 수 있습니다.
env . insert_transform ( 0 , NoopResetEnv ()) # inserts the NoopResetEnv transform at the index 0
그럼에도 불구하고 변환은 부모 환경에서 작업에 액세스하고 실행할 수 있습니다.
transform = env . transform [ 1 ] # gathers the second transform of the list
parent_env = transform . parent # returns the base environment of the second transform, i.e. the base env + the first transform
분산 학습을위한 다양한 도구 (예 : 메모리 매핑 텐서) (2) ;
다양한 아키텍처 및 모델 (예 : 액터 크리티어) (1) :
# create an nn.Module
common_module = ConvNet (
bias_last_layer = True ,
depth = None ,
num_cells = [ 32 , 64 , 64 ],
kernel_sizes = [ 8 , 4 , 3 ],
strides = [ 4 , 2 , 1 ],
)
# Wrap it in a SafeModule, indicating what key to read in and where to
# write out the output
common_module = SafeModule (
common_module ,
in_keys = [ "pixels" ],
out_keys = [ "hidden" ],
)
# Wrap the policy module in NormalParamsWrapper, such that the output
# tensor is split in loc and scale, and scale is mapped onto a positive space
policy_module = SafeModule (
NormalParamsWrapper (
MLP ( num_cells = [ 64 , 64 ], out_features = 32 , activation = nn . ELU )
),
in_keys = [ "hidden" ],
out_keys = [ "loc" , "scale" ],
)
# Use a SafeProbabilisticTensorDictSequential to combine the SafeModule with a
# SafeProbabilisticModule, indicating how to build the
# torch.distribution.Distribution object and what to do with it
policy_module = SafeProbabilisticTensorDictSequential ( # stochastic policy
policy_module ,
SafeProbabilisticModule (
in_keys = [ "loc" , "scale" ],
out_keys = "action" ,
distribution_class = TanhNormal ,
),
)
value_module = MLP (
num_cells = [ 64 , 64 ],
out_features = 1 ,
activation = nn . ELU ,
)
# Wrap the policy and value funciton in a common module
actor_value = ActorValueOperator ( common_module , policy_module , value_module )
# standalone policy from this
standalone_policy = actor_value . get_policy_operator ()
탐사 래퍼 및 모듈은 탐사와 착취 사이를 쉽게 바꾸는 모듈 (1) :
policy_explore = EGreedyWrapper ( policy )
with set_exploration_type ( ExplorationType . RANDOM ):
tensordict = policy_explore ( tensordict ) # will use eps-greedy
with set_exploration_type ( ExplorationType . DETERMINISTIC ):
tensordict = policy_explore ( tensordict ) # will not use eps-greedy
일련의 효율적인 손실 모듈과 고도로 벡터화 된 기능 반환 및 이점 계산.
from torchrl . objectives import DQNLoss
loss_module = DQNLoss ( value_network = value_network , gamma = 0.99 )
tensordict = replay_buffer . sample ( batch_size )
loss = loss_module ( tensordict )
from torchrl . objectives . value . functional import vec_td_lambda_return_estimate
advantage = vec_td_lambda_return_estimate ( gamma , lmbda , next_state_value , reward , done , terminated )
앞서 언급 한 교육 루프를 실행하는 일반 트레이너 클래스 (1) . 훅킹 메커니즘을 통해 주어진 시간에 로깅 또는 데이터 변환 작업도 지원합니다.
배치되는 환경에 해당하는 모델을 구축하는 다양한 레시피.
라이브러리에서 기능이 누락되었다고 생각되면 문제를 제출하십시오! 새로운 기능에 기여하려면 기부금 및 기부 페이지를 확인하십시오.
일련의 최첨단 구현이 예시적인 목적으로 제공됩니다.
연산 | 지원 지원 ** | 긴장된 API | 모듈 식 손실 | 연속적이고 개별 |
DQN | 1.9x | + | NA | + (ActionDiscretizer 변환을 통해) |
DDPG | 1.87x | + | + | - (연속 전용) |
IQL | 3.22x | + | + | + |
CQL | 2.68x | + | + | + |
TD3 | 2.27x | + | + | - (연속 전용) |
TD3+BC | 테스트되지 않았습니다 | + | + | - (연속 전용) |
A2C | 2.67x | + | - | + |
PPO | 2.42x | + | - | + |
낭 | 2.62x | + | - | + |
redq | 2.28x | + | - | - (연속 전용) |
몽상가 V1 | 테스트되지 않았습니다 | + | + (다른 클래스) | - (연속 전용) |
의사 결정 변압기 | 테스트되지 않았습니다 | + | NA | - (연속 전용) |
크로스 Q | 테스트되지 않았습니다 | + | + | - (연속 전용) |
게일 | 테스트되지 않았습니다 | + | NA | + |
임팔라 | 테스트되지 않았습니다 | + | - | + |
IQL (marl) | 테스트되지 않았습니다 | + | + | + |
DDPG (Marl) | 테스트되지 않았습니다 | + | + | - (연속 전용) |
PPO (Marl) | 테스트되지 않았습니다 | + | - | + |
qmix-vdn (marl) | 테스트되지 않았습니다 | + | NA | + |
SAC (marl) | 테스트되지 않았습니다 | + | - | + |
RLHF | NA | + | NA | NA |
**이 숫자는 CPU에서 실행될 때 열성적인 모드에 비해 예상 속도를 나타냅니다. 숫자는 아키텍처 및 장치에 따라 다를 수 있습니다.
그리고 더 많은 것들!
장난감 코드 스 니펫 및 교육 스크립트를 표시하는 코드 예제도 사용할 수 있습니다.
다양한 구성 설정 처리에 대한 자세한 내용은 예제 디렉토리를 확인하십시오.
우리는 또한 도서관이 할 수있는 일을 의미하는 튜토리얼과 데모를 제공합니다.
Torchrl을 사용하는 경우이 Bibtex 항목을 참조 하여이 작업을 인용하십시오.
@misc{bou2023torchrl,
title={TorchRL: A data-driven decision-making library for PyTorch},
author={Albert Bou and Matteo Bettini and Sebastian Dittert and Vikash Kumar and Shagun Sodhani and Xiaomeng Yang and Gianni De Fabritiis and Vincent Moens},
year={2023},
eprint={2306.00577},
archivePrefix={arXiv},
primaryClass={cs.LG}
}
패키지가 설치 될 콘다 환경을 만듭니다.
conda create --name torch_rl python=3.9
conda activate torch_rl
Pytorch
당신이 만들고자하는 functorch의 사용에 따라 최신 (야간) Pytorch 릴리스 또는 최신 안정 버전의 Pytorch를 설치할 수 있습니다. pip3
또는 기타 특별 설치 지침을 포함한 자세한 명령 목록은 여기를 참조하십시오.
Torchrl
사용하여 최신 안정 릴리스를 설치할 수 있습니다
pip3 install torchrl
Linux, Windows 10 및 OSX (Intel 또는 Silicon Chips)에서 작동해야합니다. 특정 Windows 시스템 (Windows 11)에서 라이브러리를 로컬로 설치해야합니다 (아래 참조).
야간 빌드를 통해 설치할 수 있습니다
pip3 install torchrl-nightly
우리는 현재 Linux 및 OSX (Intel) 기계 용으로만 배송됩니다. 중요하게도, 야간 빌드에는 야간의 Pytorch 빌드도 필요합니다.
추가 종속성을 설치하려면 전화하십시오
pip3 install " torchrl[atari,dm_control,gym_continuous,rendering,tests,utils,marl,open_spiel,checkpointing] "
또는 이들의 하위 집합.
라이브러리를 로컬로 설치하기를 원할 수도 있습니다. 세 가지 주요 이유는 다음과 같은 동기를 부여 할 수 있습니다.
로컬로 라이브러리를 설치하려면 Repo를 복제하여 시작하십시오.
git clone https://github.com/pytorch/rl
빌드에 사용하려는 지점이나 태그를 확인하는 것을 잊지 마십시오.
git checkout v0.4.0
Torchrl Repo를 복제 한 디렉토리로 이동하여 설치 한 후 ( ninja
설치 한 후)
cd /path/to/torchrl/
pip3 install ninja -U
python setup.py develop
또한 동료에게 배포하기 위해 바퀴를 만들 수 있습니다.
python setup.py bdist_wheel
바퀴는 거기에 저장됩니다 ./dist/torchrl<name>.whl
.
pip install torchrl < name > .whl
경고 : 불행히도 pip3 install -e .
현재 작동하지 않습니다. 이 문제를 해결하는 데 도움이되는 기여를 환영합니다!
M1 머신에서는 야간 Pytorch 빌드와 함께 상자 밖으로 작동해야합니다. MacOS M1 에서이 아티팩트 생성이 올바르게 작동하지 않거나 실행 중에는 메시지 (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e'))
나타나면 시도한 다음 시도하십시오.
ARCHFLAGS="-arch arm64" python setup.py develop
빠른 정신 점검을 실행하려면 해당 디렉토리 (예 : cd ~/
실행하여)를 남겨두고 라이브러리를 가져 오십시오.
python -c "import torchrl"
경고 나 오류를 반환해서는 안됩니다.
선택적 종속성
Torchrl의 사용에 따라 다음 라이브러리를 설치할 수 있습니다.
# diverse
pip3 install tqdm tensorboard "hydra-core>=1.1" hydra-submitit-launcher
# rendering
pip3 install moviepy
# deepmind control suite
pip3 install dm_control
# gym, atari games
pip3 install "gym[atari]" "gym[accept-rom-license]" pygame
# tests
pip3 install pytest pyyaml pytest-instafail
# tensorboard
pip3 install tensorboard
# wandb
pip3 install wandb
문제 해결
ModuleNotFoundError: No module named 'torchrl._torchrl
(또는 C ++ 바이너리를로드 할 수 없음을 나타내는 경고) C ++ 확장이 설치되지 않았거나 찾을 수 없음을 의미합니다.
develop
모드에 설치되지 않은 경우 다음 코드 스 니펫은 오류를 반환해야합니다. cd ~/path/to/rl/repo
python -c 'from torchrl.envs.libs.gym import GymEnv'
python setup.py develop
후 로그를 확인하십시오. 일반적인 원인 중 하나는 G ++/C ++ 버전 불일치 및/또는 ninja
라이브러리의 문제입니다. wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py
python collect_env.py
OS: macOS *** (arm64)
OS: macOS **** (x86_64)
버전 작성 문제는 undefined symbol
의 오류 메시지를 유발할 수 있습니다. 이에 대해서는 완전한 설명 및 제안 된 해결 방법에 대해서는 버전 관리 문제 문서를 참조하십시오.
라이브러리에서 버그를 발견하면이 repo에서 문제를 제기하십시오.
Pytorch의 RL에 관한보다 일반적인 질문이 있다면 Pytorch 포럼에 게시하십시오.
Torchrl에 대한 내부 협업을 환영합니다! 자유롭게 포크, 문제 및 PR을 제출하십시오. 자세한 기여 가이드를 여기에서 확인할 수 있습니다. 위에서 언급했듯이 공개 기여 목록은 여기에서 찾을 수 있습니다.
기고자는 사전 커밋 후크를 설치하는 것이 좋습니다 ( pre-commit install
사용). 사전 커밋은 코드가 로컬로 커밋 될 때 관련 문제를 줄입니다. git commit -m <commit message> -n
명령에 -n
추가하여 확인 할 수 있습니다.
이 라이브러리는 Pytorch 베타 기능으로 출시됩니다. BC 차단 변경이 발생할 가능성이 있지만 몇 번의 릴리스주기 후에도 감가 상각 보증으로 도입 될 것입니다.
Torchrl은 MIT 라이센스에 따라 라이센스가 부여됩니다. 자세한 내용은 라이센스를 참조하십시오.