이 저장소는 한국 과학기술정보통신부가 주관한 "인공지능 그랜드 챌린지 5차 1단계 대회"에 제출할 소스코드를 담고 있습니다.
$ python make_dataset.py
$ python train_model.py --name MODEL_NAME -iter 1 -data resources/new_dataset/dataset.json --opt-lr 0.00088 0.00125 0.00176 0.0025 --decoder-layer 2 4 6
$ python main.py
전체 프로젝트는 팀원의 역할에 따라 4개의 부분으로 구성됩니다.
참고: 각 파트는 소스코드를 수정할 때 반드시 아래 부분에 각 파일의 역할을 기술해주세요.
-
System Integration part: 추론 시스템의 학습용 코드 및 추론 구동을 위한 코드를 포함하는 부분입니다.
/learner
디렉토리에 추론 시스템의 학습을 위한 내부 코드를 넣어둡니다./evaluate
디렉토리에 추론과정 구동을 위한 내부 코드를 넣어둡니다./common
디렉토리는 여러 부분이 공유하는 데이터를 넣어둡니다./common/sys
디렉토리는 System integration이 사용할 상수 값 등을 담습니다. (problem 파일 위치 등)/common/model
디렉토리는 Model part에 관한 상수 값 등을 담습니다. (Hyperparameter key 등)/common/solver
디렉토리는 Solver part에 관한 상수 값 등을 담습니다. (Operator, operand 정의 등)/common/simulate
디렉토리는 Data simulator에 관한 상수 값 등을 담습니다. (YAML key 등)
/requirements.txt
파일은 PyPI requirement 정의 파일입니다.- 참고: ray는 학습에만 사용하므로 requirements.txt에는 넣지 않았습니다.
/main.py
파일은 챌린지 사무국이 지정한 추론 구동을 위한 실행 파일입니다./train_model.py
파일은 추론 시스템의 학습을 수행하는 실행 파일입니다.- 참고: Model part는 이 파일만 사용해 debugging 해도 충분합니다. (
/main.py
와 코드를 공유합니다.)
- 참고: Model part는 이 파일만 사용해 debugging 해도 충분합니다. (
/check_dataset.py
파일은 Dataset의 정합성을 평가하는 파일입니다.- 참고: Solver part는 이 파일을 사용해 debugging 합니다.
/make_dataset.py
파일은 학습 및 평가(development)에 사용할 예제를 생성하는 파일입니다.- 참고: Data simulator part는 이 파일을 사용해 debugging합니다.
-
Model part: 추론 시스템이 사용하는 EPT 모델과 관련 코드를 정의합니다.
-
/model
디렉토리에 EPT 모델을 구성하는 내부 코드를 넣어둡니다.- 참고: 파이썬 기본 라이브러리를 제외하면 다음 라이브러리만을 사용합니다.
torch, numpy, transformers
- Optimization 관련 코드는 system integration으로 이동됩니다.
- 참고: 파이썬 기본 라이브러리를 제외하면 다음 라이브러리만을 사용합니다.
-
필요한 경우
/common/model
을 수정할 수 있습니다. 단, 수정할 경우 이 코드에 의존하는 System integration part에 알려야 합니다. (/train_model.py
나/model.py
를 직접 수정하지 않습니다!)
-
-
Solver part: EPT가 출력하는 expression sequence를 python code로 변환하고, 답을 도출합니다.
/solver
디렉토리에 Solver를 구성하는 내부 코드를 넣어둡니다.- 필요한 경우
/common/solver
를 수정할 수 있습니다. 단, 수정할 경우 이 코드에 의존하는 System integration part와 Data simulator part에 알려야 합니다. (다른 부분의 코드를 직접 수정하지 않습니다!)
-
Data simulator part: 학습에 사용할 data를 simulate하는 코드를 정의합니다.
/simulate
디렉토리에 Simulator를 구성하는 내부 코드를 넣어둡니다.- 필요한 경우
/common/simulate
를 수정할 수 있습니다. 단, 수정할 경우 System integration part에 알려야 합니다. (/make_dataset.py
를 직접 수정하지 않습니다!)
모든 call은 system integration part에서 발생하고, model/solver/simulator part는 서로 독립적입니다.
따라서, model이 solver를 호출하거나, simulator가 solver를 호출하는 등의 상황은 발생하지 않습니다.
이 부분은 System Integration에서 우선 구현합니다
class Text:
#: 토큰 인덱스. 항상 [B, S], B는 Batch size, S는 토큰 길이(가변)
tokens: torch.LongTensor
#: 토큰 별 단어 인덱스. tokens와 길이 동일. common.model.const.PAD_ID 값은 단어가 아닌 경우.
word_indexes: torch.LongTensor
#: 각 단어에 관한 정보. len(word_info) == B, len(word_info[i]) == word_indexs[i].max().
word_info: List[List[Dict[str, Any]]]
# 'is_num'(common.sys.key.IS_NUM)은 단어가 십진숫자인지의 여부 (True/False)
# 'is_var'(common.sys.key.IS_VAR)는 단어가 미지수[A, B, C, ..., Z]인지의 여부
# 'is_prop'(common.sys.key.IS_PROP)는 단어가 고유명사[(가), ... 정국, ...]인지의 여부
# 'value'(common.sys.key.VALUE)는 단어의 불필요한 부분을 제외한 어근의 값 (string)
# 'word'(common.sys.key.WORD)는 단어 자체
class Expression:
#: 연산자/함수명 인덱스. 항상 [B, T], B는 Batch size, T는 토큰 길이(가변).
operator: torch.LongTensor
#: 피연산자의 목록. operator와 길이 동일. common.model.const.PAD_ID는 정의되지 않은 경우.
operands: List[torch.LongTensor]
System Integration part는 EPT
class (model.EPT
)에 다음 함수가 있을 것이라 가정합니다.
참고 원본 EPT 코드를 사용한다면 아래 함수가 정의되어 있을 것입니다.
def forward(self, text: Text, expression: Expression = None, beam: int = 3) -> dict:
"""
추론을 진행합니다.
:param common.model.types.Text text:
텍스트 관련 정보를 담고 있습니다. 필수입니다.
:param common.model.types.Expression expression:
훈련 상황에서는 생성할 목표 수식을 담고 있습니다. 평가 상황에서는 None으로 주어집니다.
훈련 상황은 self.training == True를, 반대는 평가 상황을 지칭합니다.
:param int beam:
평가 상황에서 사용할 Beam size
:rtype dict:
:returns:
다음 항목을 key로 갖는 dictionary를 반환합니다.
- 'prediction': common.model.types.ExpressionPrediction
훈련 상황에만 생성합니다. 입력 expression의 다음 token들의 log-확률입니다.
오타를 방지하기 위해서 이 키 값은 common.model.key.PREDICTION으로 참조합니다.
- 'expression': common.model.types.Expression
평가 상황에만 생성합니다. 입력된 text에 맞는 expression으로,
beam search의 결과로 나온 것 중 top-1만 선택합니다.
오타를 방지하기 위해서 이 키 값은 common.model.key.EXPRESSION으로 참조합니다.
"""
def save(self, directory: str):
"""
checkpoint 또는 저장을 수행합니다.
model.ept.checkpoint.CheckpointingModule로부터 상속받으므로,
별도의 변경작업은 필요치 않습니다.
"""
@classmethod
def create_or_load(self, path: str = None, **config) -> EPT:
"""
checkpoint 또는 저장된 객체로부터 생성하기 위한 class-level method입니다.
어떤 config가 필요한지 꼭 문서화 해주세요.
"""
- Beam search 과정에서 operator별로 필요한 operand의 개수가 다르거나, operator별로 제한조건이 가해질 수 있습니다.
예를 들어, operator가 숫자만 operand로 값을 취하는 경우, 이 값이 숫자인지, 또는 그 연산 결과가 숫자인지 확인해야 합니다.
각 operator별 specification 정의는 Solver part에서 담당하며,
/common/solver
에 저장됩니다.
class Execution:
#: 사용하고자 하는 함수/연산자의 이름
function: int
#: 함수/연산자의 인자로 들어가는 값들.
arguments: List[Tuple[int, int]]
# 하나의 Tuple은 (값의 타입, 값의 위치)를 나타냄.
# - (0, i)는 사전 정의된 i번째 상수값
# - (1, i)는 문제 속 i번째 단어에 해당하는 숫자 또는 문자열 값
# - (2, i)는 지금까지 계산한 결과값 중 i번째 결과값
System Integration part는 solver
package (solver
)에 다음 함수가 있을 것이라 가정합니다.
def python_code_to_executions(code_template: str) -> List[Execution]
"""
주어진 python code를 execution으로 변환합니다.
python code에 주어진 _i는 문제 속 i번째 단어를 지칭합니다.
예를 들어, `for _9 in range(int(_11), int(_13))`와 같이 표시될 수 있습니다.
Execution으로 변환할 때에는 이를 적절한 argument type으로 변경합니다.
또, python code는 macro를 포함할 수 있습니다.
:param str code_template: 변환할 python code 또는 이에 준하는 코드
:rtype: List[Execution]
:return: 주어진 코드에 맞는 list of Execution.
"""
def execution_to_python_code(expression: List[Execution],
word_mappings: List[Dict[str, Any]], indent: int = 4) -> str:
"""
결과로 획득한 expression을 python code로 변환합니다.
:param List[Execution] expression:
Execution 객체의 List입니다.
:param List[Dict[str,Any]] word_mappings:
텍스트의 각 단어마다 숫자 값 또는 단어를 담고 있는 list입니다.
'is_num'(common.sys.key.IS_NUM)은 단어가 십진숫자인지의 여부 (True/False)
'is_var'(common.sys.key.IS_VAR)는 단어가 미지수[A, B, C, ..., Z]인지의 여부
'is_prop'(common.sys.key.IS_PROP)는 단어가 고유명사[(가), ... 정국, ...]인지의 여부
'value'(common.sys.key.VALUE)는 단어의 불필요한 부분을 제외한 어근의 값 (string)
'word'(common.sys.key.WORD)는 단어 자체
:param int indent:
Indentation에 사용할 space 개수입니다.
:rtype: str
:return:
주어진 execution 순서에 맞는 Python code string입니다.
사람이 읽을 수 있도록 '_i' 변수명은 모두 적절한 string 값 또는 변수명으로 치환됩니다.
"""
- 변수를 생성할 때, 한국어가 들어오면 그에 맞는 로마자 표기를 사용해야 합니다. (한국어 변수명 사용 불가)
- AST의 구조를 완벽하게 따를 필요는 없지만, 최소한 AST의 부분집합과는 호환되어야 합니다.
- 파이썬 코드의 실행 부분은 System integration에서 작성하므로 작성하지 않습니다.
class Problem:
#: 생성되는 문제 1개의 텍스트 string
text: str
#: 문제가 의도한 정답
answer: str
#: 답을 구하기 위한 Python code moo_lang
code_template: str
# 문제의 i번째 단어를 변수명으로 사용하거나, 값으로 사용하는 경우, _i와 같이 참조.
# _i가 변수명으로 사용되거나 string으로 사용되는 경우는 별도의 casting을 하지 않으며,
# int나 float으로 사용되는 경우는 int(_i) 또는 float(_i)와 같이 작성
# Solver part와 협의 하에, 반복되는 코드는 macro를 사용해 간결하게 만들 수 있습니다.
System integration part는 Simulator
class (simulator.Simulator
)에 다음 함수가 있을 것이라 가정합니다.
def load_templates(self, templates: List[dict]):
"""
주어진 템플릿 설정의 list를 읽어 template을 등록합니다.
:param List[dict] templates: 각 template의 설정값
"""
def set_seed(self, seed: int):
"""
난수생성기의 초기값을 seed로 초기화합니다.
:param int seed: 초기값
"""
def generate(self, n: int) -> List[List[Problem]]:
"""
각 template마다 n개의 새로운 문제를 생성합니다.
:param int n: moo_lang 당 생성될 문제의 개수
"""
- 같은 seed로 생성되는 데이터는 같은 순서로 같은 결과가 나와야 합니다.
- 생성되는 code_template은 Solver로 번역된 다음에는 실행이 가능한 코드여야 합니다.
Load model from './weights/checkpoint' using model.EPT.create_or_load()
Move model to GPU if available
Set model as evaluation mode
Read '/home/agc2021/dataset/problemsheet.json' and store (key, text) pairs into problems
Initialize answers as dict
For each text in problems
Transform text into common.model.types.Text instance
Generate equation using model.forward()
Transform equation into a list of common.solver.types.Execution
/* The following two lines will be shared with train_model.py, check_dataset.py */
Transform equation into python code using solver.execution_to_python_code()
Execute python code with timeout (0.5s) and get an answer (type: string)
Set answers[key] as {'answer': answer, 'equation': code}
Dump answers into './answersheet.json'
Finalize everything
/* Note: actual implementation will use parallelization.*/
Read command-line arguments, including datasetpath, checkpointpath, hyperparam
Read dataset from datasetpath (i.e., generate all required data types for each problem)
Create model based on hyperparam, using model.EPT.create_or_load()
Move model to GPU if available
Set model as training mode
Set dataset as training mode
/* Training */
For i in 0...T:
Create a list of mini-batches
For b in mini-batches:
Generate equation using model.forward()
Compare equation with gold-standard and compute loss
Update model by back-propagation
Store the model to checkpointpath, using model.save()
Store the tokenizer to checkpointpath
/* Testing */
Set dataset as testing mode
Set correct problems as 0
For item in dataset:
/* The following two lines will be shared with train_model.py, check_dataset.py */
Transform equation into python code using solver.execution_to_python_code()
Execute python code with timeout (0.5s) and get an answer (type: string)
Verify whether the answer is the same as expected one
if same
Add 1 to correct problems
Print or log the accuracy
Read command-line arguments, including datasetpath
Read dataset from datasetpath (i.e., generate all required data types for each problem)
For item in dataset:
Extract execution of the item
Transform equation into a list of common.solver.types.Execution
/* The following two lines will be shared with train_model.py, main.py */
Transform equation into python code using solver.execution_to_python_code()
Execute python code with timeout (0.5s) and get an answer (type: string)
Verify whether the answer is the same as expected one
if not same
Report the exception
Read command-line arguments, including templateroot, numitems, datasetpath, seed
Create a simulator of type simulator.Simulator
Register seed using simulator.set_seed()
Read templates from templateroot
Register templates using simulator.load_templates()
Get generated items using simulator.generate()
Store generated items into datasetpath
- Read (text, python macro, answer) from the dataset
- Transformation spec
- text = common.model.types.Text
- python macro = common.model.types.Expression
- answer = str
{
"ID (string)": {
"question": "문제 텍스트 (String)",
"answer": "정답 (String)",
"equation": "실행해야 하는 프로그램 코드 (String)",
"execution": [ // Number(int)의 list의 list
[f0, s00, a00, s01, a01, ...], // f0는 operator index, s00는 operand0의 source index, a00은 해당 source에서 operand0의 index
[f1, s10, a10, s11, a11, ...],
...
]
}
}
- source index에 대해서는
common.sys.key.SRC_*
참고 - 함수 f가 argument를 N개 받는다면, execution은 2N+1개의 항목이 있어야 함
- 실제 훈련 과정에서는 equation은 사용하지 않음
- dataset.json이 위치하는 디렉터리 속
split
폴더에train
,dev
,test
명칭으로 존재함 - 각 행은 이 split에 속하는 ID 값이며, 행 구분자는
\n
.
pip3 install torch==1.8.1+cu111 -f https://download.pytorch.org/whl/torch_stable.html
python3 -m pip install transformers==4.6.1 sympy==1.8
- 환경구성 오류) [도커파일빌드] 도커 이미지 전송에 실패하였습니다. 잠시 후 다시 시도해주세요. 도커 이미지 전송은 종종 네트워크 환경으로 인해 타임아웃이 발생할 수 있습니다.:
- 채점 오류) invalid character 'ï' looking for beginning of value
- 이하 환경 구성 오류
- [도커파일빌드] 빌드가 실패하였습니다. 잘못된 설치 옵션 명령어를 넣었는지 다시 한번 확인해주세요:
...(중략)... n compiling Pillow from source. Please see the install instructions at: https://pillow.readthedocs.io/en/latest/installation.html ---------------------------------------- �[0m �[91mERROR: Command errored out with exit status 1: /home/agc2021/.pyenv/versions/3.8.3/bin/python3.8 -u -c import sys, setuptools, tokenize; sys.argv[0] = /tmp/pip-install-v0mblh86/pillow/setup.py; __file__=/tmp/pip-install-v0mblh86/pillow/setup.py; f=getattr(tokenize, open, open)(__file__); code=f.read().replace(\r\n, \n); f.close(); exec(compile(code, __file__, exec)) install --record /tmp/pip-record-b6w9ejng/install-record.txt --single-version-externally-managed --compile Check the logs for full command output. �[0m �[91mWARNING: You are using pip version 19.2.3, however version 21.3.1 is available. You should consider upgrading via the pip install --upgrade pip command. �[0m Removing intermediate container a554a2b45b60
- [도커파일생성] 프로젝트 구조가 올바르지 않습니다. 압축을 해제한 디렉토리 구조가 규정과 일치하는지 다시 한번 확인해주세요. - 디렉토리에 프로젝트 폴더가 존재하지 않습니다.
- [추론모델실행] answersheet_5_00_{teamID}.json 파일이 없습니다.
Traceback (most recent call last): File "main.py", line 16, in model = EPT.create_or_load(str(EVALUATE_WEIGHT_DIR.absolute())) File "/home/agc2021/model/ept/chkpt.py", line 29, in create_or_load model.load_state_dict(state) File "/home/agc2021/.pyenv/versions/3.8.3/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1223, in load_state_dict raise RuntimeError('Error(s) in loading state_dict for {}:\n\t{}'.format( RuntimeError: Error(s) in loading state_dict for EPT: size mismatch for decoder.operator_word_embedding.weight: copying a param with shape torch.Size([41, 768]) from checkpoint, the shape in current model is torch.Size([40, 768]). size mismatch for operator.weight: copying a param with shape torch.Size([41, 768]) from checkpoint, the shape in current model is torch.Size([40, 768]). size mismatch for operator.bias: copying a param with shape torch.Size([41]) from checkpoint, the shape in current model is torch.Size([40]).
- [추론모델실행] answersheet_5_00_{teamID}.json 파일이 없습니다.
Traceback (most recent call last): File "main.py", line 16, in model = EPT.create_or_load(str(EVALUATE_WEIGHT_DIR.absolute())) File "/home/agc2021/model/ept/chkpt.py", line 29, in create_or_load model.load_state_dict(state) File "/home/agc2021/.pyenv/versions/3.8.3/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1223, in load_state_dict raise RuntimeError('Error(s) in loading state_dict for {}:\n\t{}'.format( RuntimeError: Error(s) in loading state_dict for EPT: size mismatch for decoder.operator_word_embedding.weight: copying a param with shape torch.Size([41, 768]) from checkpoint, the shape in current model is torch.Size([47, 768]). size mismatch for operator.weight: copying a param with shape torch.Size([41, 768]) from checkpoint, the shape in current model is torch.Size([47, 768]). size mismatch for operator.bias: copying a param with shape torch.Size([41]) from checkpoint, the shape in current model is torch.Size([47]).
- [추론모델실행] answersheet_5_00_{teamID}.json 파일이 없습니다.
Traceback (most recent call last): File "main.py", line 37, in instance = string_to_text_instance(text[QUESTION], tokenizer) File "/home/agc2021/common/sys/convert.py", line 87, in string_to_text_instance assert string_left.startswith(token_string) AssertionError