파인 튜닝(Fine-tuning)

“Fine”이라는 단어는 “미세한” 또는 “세밀한”이라는 의미를 가지고 있습니다. “Tuning”은 조정 또는 조절을 의미합니다. 따라서 “Fine-tuning”은 “미세하게 조정” 또는 “세밀하게 조절”하는 것을 의미합니다.

사전 훈련된 모델을 Fine-tuning하는 과정에서는 초기에 학습된 모델의 가중치를 작업에 맞게 조정하거나 조절합니다. 이 과정은 초기 모델의 일반적인 특징을 유지하면서 작업에 필요한 세부사항을 조정하여 최적화하는 것을 의미합니다. 따라서 “Fine-tuning”은 초기 모델의 가중치를 미세하게 조정하여 작업에 최적화하는 것을 의미합니다.

즉, “Fine-tuning”은 단어적 의미로서 초기 모델의 가중치를 세밀하게 조정하는 과정을 의미합니다.

파인 튜닝은 일반적으로 다음과 같은 단계로 이루어집니다:

1. 사전 훈련된 모델 가져오기: 사전 훈련된 모델(예: BERT, GPT)을 가져옵니다. 이 모델은 일반적인 언어 이해 능력을 갖추고 있습니다.

2. 추가적인 레이어 추가: 특정 작업을 수행하기 위해 모델의 상위에 추가적인 레이어를 추가합니다. 이 레이어는 특정 작업에 필요한 특징을 학습하고 예측을 수행합니다.

3. 작업에 맞는 데이터셋으로 훈련: 파인 튜닝을 위해 작업에 맞는 데이터셋을 사용하여 모델을 추가로 훈련합니다. 작업에 따라 지도 학습이나 강화 학습 등의 방법을 사용할 수 있습니다.

4. 모델 업데이트 및 성능 평가: 파인 튜닝된 모델을 업데이트하고 작업의 성능을 평가합니다. 필요에 따라 추가 훈련이나 하이퍼파라미터 조정 등을 반복할 수 있습니다.

파인 튜닝은 사전 훈련된 모델의 지식을 이용하여 작업에 맞는 모델을 구축하는 데에 유용하게 활용됩니다. 사전 훈련된 모델은 대용량 데이터와 많은 컴퓨팅 자원을 필요로 하지만, 파인 튜닝은 비교적 작은 규모의 작업 데이터와 적은 컴퓨팅 자원으로도 좋은 성능을 얻을 수 있도록 도와줍니다. 이를 통해 다양한 작업에 적용 가능한 유연하고 효율적인 인공지능 모델을 개발할 수 있습니다.

스탠포드 알파카(Alpaca) 코드분석 – 누구나 챗GPT 3.5성능의 모델을 만들 수 있다. 1 generate_instruction.py

알파카 모델은 메타에서 공개한 라마 7B 모델을 가져와 Instruction-following 작업을 수행하기 위해 추가적인 훈련(파인튜닝)을 받은 모델입니다.
주의 * Alpaca는 학술 연구만을 목적으로 하며 어떠한 상업적 사용도 금지되어 있음을 강조합니다 . (참고 알파카 블로그)

아래 그림은 Alpaca 모델을 얻은 방법을 보여줍니다. 데이터를 위해 우리는 self-instruct seed set 에서 175개의 사람이 작성한 명령-출력 쌍으로 시작했습니다 . (아래 글에도 설명이 나옵니다.) 그런 다음 text-davinci-003에 컨텍스트 내 예제로 시드 세트를 사용하여 추가 지침을 생성하도록 요청했습니다. 생성 파이프라인( GitHub 의 세부 정보 참조)을 단순화하여 자체 지시 방법을 개선 하고 비용을 크게 줄였습니다. 우리의 데이터 생성 프로세스는 52,000개의 고유한 명령과 해당 출력을 생성하며 OpenAI API를 사용하여 $500 미만의 비용이 듭니다.

즉, 다음의 순서입니다.

1.스탠포드 알파카(Alpaca) 코드의  seed_tasks.jsonl 의 175개의 데이터가 있습니다.코드에서 `seed_tasks`는 사전 훈련된 모델을 Fine-tuning하기 위한 초기 데이터로 사용되는 작업의 집합을 나타냅니다.

`seed_tasks`는 사전에 수집된 작업 데이터를 포함하고 있으며, 각 작업은 “시작점” 또는 “초기 상태”로 사용됩니다. 이러한 작업은 사전 훈련된 모델을 특정 작업에 맞게 Fine-tuning하는 데 사용됩니다. 각 작업에는 인간이 작성한 명령문(instruction)과 해당 작업에 대한 입력(input) 및 출력(output) 데이터가 포함될 수 있습니다.

일반적으로 `seed_tasks`는 작은 규모의 초기 데이터셋으로 시작하여 Fine-tuning 과정을 수행합니다. 이러한 초기 데이터는 모델에 필요한 작업에 대한 예시를 제공하고 모델이 해당 작업을 이해하고 수행하는 데 도움을 줍니다. Fine-tuning을 통해 모델은 이러한 초기 데이터에 대한 학습을 거쳐 작업에 더 적합하고 정확한 예측을 수행할 수 있게 됩니다.

따라서 `seed_tasks`는 Fine-tuning 작업에 사용되는 초기 데이터셋을 의미합니다.

2.이것을 아래 그림의 Text-davinci-003을 이용합니다.

3. 그렇게 해서 총 5만2000개를 생성 합니다. 이것을 가지고 아래 그림의 LLaMA 모델을 이용해 미세 조정합니다.초기 실행에서 7B LLaMA 모델을 미세 조정하는 데 8개의 80GB A100에서 3시간이 걸렸으며 대부분의 클라우드 컴퓨팅 공급자에서 100달러 미만입니다. 교육 효율성을 개선하여 비용을 더욱 절감할 수 있습니다.

그렇게 해서 그림 처럼 알파카모델이 탄생되었다는 것입니다.

 

 

그럼 이렇게 52000개의 데이터를 만들어 내는 Self -Instruct 과정의 핵심 코드를 보겠습니다.  Self -Instruct  는 만약 사람이 52000개의 데이터를 만들어 내려면 얼마나 힘들겠습니까.  그래서 언어모델 자체가 생성한 문장을 학습할 수 있게 하는 것이죠.

그래서 Self Instruct는 스스로(Self) 지시를(Instruct) 언어모델을 통해서 만들어 내는 것입니다.

과정을 안내하는 논문의 그림입니다. 논문의 2페이지에 있습니다.

그림 설명입니다. 175개의 seed task입니다. seed_tasks.jsonl  를 보시면 설명처럼 타스크는 각각 하나의 지시와 하나의 인스탄스를 가집니다.(인스탄스는 인풋과 아웃풋을 가지고 있습니다.) 그리고 분류문제인지 여부에 대한 데이터를 가집니다.

이것을 Task Pool에 넣어 LM(언어모델)이 문장을 생성합니다. (Step 1)그리고 분류문제 인지 여부를 판단해 (Step 2) 타스크가 분류가 되는 경우 Class Label 이 있는 타스크, 아니면 그냥  타스크(인스트럭션과 인풋, 아웃풋만 있는)로 나눕니다. (Step 3)

그리고 나서 Step 4에서 필터링을 통해 적절하지 않은 문장을 걸러내도록 합니다.

그리고 나서 다시 필터링된 걸러진 문장을 또다시 Task Pool에 입력해서 위의 과정을 다시 반복합니다. 이렇게 해서 계속해서 Task Pool을 증가 시킬 수 있게 됩니다.

이때 Task Pool을 증가시키는 프롬프트 템플릿에 대한 논문의 내용을 봅니다.
논문 14페이지의 하단을 보세요. Task 9 가 비어 있습니다. 아래 새로운 인스트럭션을 만드는 프롬프트에 의해 새로운 Taks 9 가 만들어 지는것입니다.

그리고 결과물은 다양한 분야를 커버하는 것을 볼 수 있습니다.

https://github.com/shop2world/stanford_alpaca/blob/main/assets/parse_analysis.png

이제 위의 논문을 통해 설명된 Instruction following 을 코드로 구현한 LLaMA model 코드를 봅니다.

이것은 코드의 prompt.txtseed_tasks.josonl 을 이용해서 데이터 생성 구현을 위해 인스트럭션을 생성하는 generate_instruction.py 의 generate_instruction_following_data 함수를 살펴보겠습니다.

이 코드는 generate_instruction_following_data라는 함수를 정의하는 부분입니다. 함수는 다양한 매개변수를 받아들이고, 주어진 매개변수에 따라 작업을 수행하여 지시를 따르는 데이터를 생성합니다.

이 함수의 매개변수는 다음과 같습니다:

  • output_dir: 생성된 데이터를 저장할 디렉토리 경로입니다. 기본값은 현재 디렉토리("./")입니다.
  • seed_tasks_path: 시드(seed) 지시사항 작업이 포함된 JSONL 파일의 경로입니다. 기본값은 ./seed_tasks.jsonl입니다.
  • num_instructions_to_generate: 생성할 지시사항의 개수입니다. 기본값은 100입니다.
  • model_name: 사용할 GPT-3.5 모델의 이름입니다. 기본값은 "text-davinci-003"입니다.
  • num_prompt_instructions: 각 생성 요청에 포함할 프롬프트(prompt) 지시사항의 개수입니다. 기본값은 3입니다.
  • request_batch_size: 병렬로 수행할 요청의 개수입니다. 기본값은 5입니다.
  • temperature: 모델 출력의 무작위성을 조절하는 온도 매개변수입니다. 기본값은 1.0입니다.
  • top_p: 다양한 출력을 생성하기 위한 top-p(nucleus) 샘플링 매개변수입니다. 기본값은 1.0으로, 모든 가능성을 고려합니다.
  • num_cpus: 병렬화에 사용할 CPU 코어의 개수입니다. 기본값은 16입니다.

함수는 다음과 같은 단계로 진행됩니다:

1 먼저, 위에 그림에서 설명한 seed_tasks 데이터를 가져옵니다.

seed_tasks = [json.loads(l) for l in open(seed_tasks_path, “r”)]

이 코드는 seed_tasks_path로 지정된 JSONL 파일을 열어서 데이터를 로드합니다.

2 seed_tasks 데이터에서 관련 정보를 추출합니다. 그리고 seed_instruction_data 변수에 매핑된 seed_tasks데이터를 넣어 줍니다. (123 번째 줄)

seed_instruction_data = [
        {"instruction": t["instruction"], "input": t["instances"][0]["input"], "output": t["instances"][0]["output"]}
        for t in seed_tasks
    ]

이 코드는 seed_tasks 데이터에서 지시(instruction), 입력(input), 출력(output) 정보를 추출하여 seed_instruction_data에 저장합니다. 아래 그림의  1 instruction , 그리고 1 instance (입력, 출력) 입니다.

3 로드된 인간이 작성한 시드 지시사항의 개수를 출력합니다.

print(f”Loaded {len(seed_instruction_data)} human-written seed instructions”)

4 지시문이 생성될 출력 디렉토리를 생성합니다.

os.makedirs(output_dir, exist_ok=True)

5 요청을 생성하기 위한 인덱스를 초기화합니다.

request_idx = 0

6 기계 생성 지시사항(machine_instruction_data)을 로드합니다.

machine_instruction_data = []
    if os.path.exists(os.path.join(output_dir, "regen.json")):
        machine_instruction_data = utils.jload(os.path.join(output_dir, "regen.json"))
        print(f"Loaded {len(machine_instruction_data)} machine-generated instructions")

이 코드는 기계 생성 지시사항(machine_instruction_data)이 이미 생성되어 있는 경우 해당 파일을 로드합니다. regen.json은 처음에는 존재하지 않지만 나중에 생성되는 파일이 되는 것이죠. 즉, 기계 생성 지시사항(machine_instruction_data)이 있으면 가져오고 없으면 안가져옵니다.

7 Rouge 스코어링을 위한 스코어러 객체를 생성합니다. 생성한 문장의 유사도를 체크합니다.

scorer = rouge_scorer.RougeScorer([“rougeL”], use_stemmer=False)

8 새로운 지시사항을 생성합니다. 위의 num_instructions_to_generate 기본값이 100이므로 100개를 만듭니다.

while len(machine_instruction_data) < num_instructions_to_generate:
request_idx += 1
# 요청 생성과 결과 처리 과정

이 코드는 num_instructions_to_generate로 지정된 개수만큼 지시사항을 생성하는 반복문입니다. 요청 생성 및 결과 처리 과정은 반복문 내에서 수행됩니다.

이후에는 요청 생성 및 결과 처리, 유사도 계산, 생성된 지시사항 관리 등의 과정이 이어집니다.

이쯤에서 지시사항 생성을 위한 함수 generate_instruction_following_data 이 거치는 과정을 봅니다:

  1. seed_tasks.jsonl 파일로부터 seed instruction 데이터를 가져옵니다. 이 데이터는 인간이 작성한 지시사항과 해당 지시사항에 대한 입력과 출력으로 구성되어 있습니다.
  2. 지시사항 생성을 위해 LM(Language Model)이 생성한 지시사항 데이터를 불러옵니다. 이 데이터는 이전에 생성된 기계 생성 지시사항으로 구성되어 있습니다. 만약 output_dir 경로에 “regen.json” 파일이 존재한다면 해당 파일에서 기계 생성 지시사항 데이터를 로드합니다.
  3. Rouge 스코어 계산을 위해 RougeScorer 객체를 생성합니다.
  4. 새로운 지시사항을 생성하기 위해 반복문을 실행합니다. 기계 생성 지시사항 데이터의 수가 num_instructions_to_generate보다 작은 경우에만 반복문이 실행됩니다.
  5. request_batch_size만큼의 요청(batch)을 생성합니다. 각 요청은 num_prompt_instructions 개수만큼의 seed instruction 데이터를 사용하여 인코딩된 프롬프트로 구성됩니다.
  6. 생성된 요청을 LM에 전달하여 지시사항을 생성합니다. 이때 LM 모델 이름, 온도(temperature), 상위 p(top_p) 등의 매개변수를 설정하여 생성 방식을 조정할 수 있습니다.
  7. 생성된 결과를 후처리하여 새로운 지시사항 데이터를 추출합니다.
  8. 추출된 새로운 지시사항 데이터와 기존의 지시사항 데이터를 비교하여 유사도를 계산합니다. 이를 위해 Rouge 스코어를 사용합니다. 계산된 유사도는 가장 유사한 지시사항들과 함께 저장됩니다.
  9. 유사도가 일정 기준(0.7)을 넘지 않는 지시사항은 유지하고, 유사도가 높은 지시사항은 새로운 기계 생성 지시사항 데이터에 추가합니다.
  10. 생성된 지시사항의 수와 유지된 지시사항의 수를 출력합니다.
  11. 기계 생성 지시사항 데이터를 파일에 저장합니다.

이러한 과정을 거쳐서 새로운 기계 생성 지시사항 데이터가 생성됩니다.

또 이어서 보겠습니다.
145번째 줄 코드를 보겠습니다.

# first we tokenize all the seed instructions and generated machine instructions
    all_instructions = [d["instruction"] for d in seed_instruction_data] + [
        d["instruction"] for d in machine_instruction_data
    ]
    all_instruction_tokens = [scorer._tokenizer.tokenize(inst) for inst in all_instructions]

위 코드는 seed instruction과 기계 생성된 지시사항(machine_instruction_data)들을 토큰화하는 과정입니다. 둘이 더해진 변수 all_instructions 는 아래 그림의 Task Pool에 해당됩니다.

먼저, seed_instruction_data에서 seed instruction들의 텍스트 부분만 추출하여 all_instructions 리스트에 저장합니다. 이때, seed_instruction_data는 seed instruction 데이터의 리스트이며, 각각의 데이터는 다음과 같은 구조를 가지고 있습니다:

{
“instruction”: <지시사항 텍스트>,
“input”: <입력 데이터>,
“output”: <출력 데이터>
}

다음으로, machine_instruction_data에서 기계 생성된 지시사항들의 텍스트 부분을 추출하여 all_instructions 리스트에 추가합니다. 이렇게 되면 all_instructions 리스트에는 seed instruction과 기계 생성된 지시사항들의 모든 텍스트가 포함됩니다.

그 후, all_instruction_tokens 리스트를 생성합니다. 이 리스트는 각각의 지시사항 텍스트를 토큰화한 결과를 담고 있습니다. scorer._tokenizer.tokenize(inst) 코드는 주어진 지시사항 텍스트 inst를 토큰화하여 리스트 형태로 반환합니다. 따라서 all_instruction_tokens 리스트는 all_instructions 리스트에 있는 모든 지시사항들을 토큰화한 결과로 구성됩니다.

이렇게 함으로써, seed instruction과 기계 생성된 지시사항들을 모두 토큰화하여 나중에 유사도를 계산하는 데 사용할 수 있게 됩니다.

토큰화된 데이터는 리스트의 리스트 형태로 구성됩니다. 각각의 리스트는 해당 지시사항의 토큰으로 구성되어 있습니다. 예를 들어, all_instruction_tokens 리스트의 한 요소를 살펴보면 다음과 같은 형태일 수 있습니다:

[
['First', 'prompt', 'instruction', '.'],
['Second', 'prompt', 'instruction', 'with', 'more', 'tokens', '.'],
# ...
]

위 예시에서는 두 개의 지시사항이 토큰화되어 있습니다. 각각의 지시사항은 해당 지시사항을 구성하는 단어들로 토큰화되어 리스트 형태로 저장되어 있습니다. 이러한 토큰화된 데이터를 활용하여 지시사항들 간의 유사도를 계산하거나 기타 자연어처리 작업을 수행할 수 있습니다.

154번째 코드를 보겠습니다.

batch_inputs = []
        for _ in range(request_batch_size):
            # only sampling from the seed tasks 시드에서만 샘플링!
            prompt_instructions = random.sample(seed_instruction_data, num_prompt_instructions)
            prompt = encode_prompt(prompt_instructions)
            batch_inputs.append(prompt)

request_batch_size가 5로 설정되어 있기 때문에 5개의 프롬프트가 생성됩니다.

request_batch_size는 한 번의 요청(batch)에 포함될 입력 데이터의 개수를 나타내는 변수입니다. 즉, 한 번의 요청에 동시에 처리할 프롬프트의 개수를 지정하는 값입니다. 위 코드에서는 request_batch_size가 5로 설정되어 있으므로, 각 반복마다 5개의 프롬프트가 생성되고 batch_inputs 리스트에 추가됩니다.

일반적으로 request_batch_size 값을 설정하는 것은 처리 효율성과 성능을 조절하는 데에 도움이 됩니다. 한 번에 여러 개의 입력 데이터를 처리하면 처리 시간을 단축시킬 수 있으며, LM이 병렬로 작업을 수행하여 전체 처리량을 높일 수 있습니다. 하지만 더 많은 메모리 및 처리 자원이 필요하므로 적절한 값으로 조정해야 합니다.

따라서 위 코드는 `request_batch_size`에 지정된 개수인 5개의 프롬프트를 생성하는 과정입니다.

먼저, 빈 리스트인 `batch_inputs`가 초기화됩니다. 이 리스트는 생성된 프롬프트를 저장하기 위한 용도로 사용됩니다.

다음으로, `request_batch_size`에 지정된 개수만큼 반복문이 실행됩니다. 이 반복문은 요청의 개수에 따라서 5번 실행됩니다.

각 반복에서는 `seed_instruction_data`에서 `num_prompt_instructions` 개수만큼의 seed instruction 데이터를 무작위로 선택합니다. 이때, `random.sample()` 함수를 사용하여 중복 없이 랜덤하게 선택됩니다. 선택된 seed instruction 데이터는 `prompt_instructions` 변수에 저장됩니다.

그 다음, `prompt_instructions`를 이용하여 `encode_prompt()` 함수가 호출되어 프롬프트를 인코딩합니다. 이 함수는 선택된 seed instruction 데이터를 기반으로 프롬프트를 생성하고, 인코딩된 형태로 반환합니다. 이렇게 생성된 프롬프트는 `prompt` 변수에 저장됩니다. 즉, 그림에서 LM에서 프롬프트를 입력해 지시를 하는 부분이죠.

마지막으로, 생성된 프롬프트(`prompt`)를 `batch_inputs` 리스트에 추가합니다. 이렇게 되면 각 반복에서 생성된 프롬프트가 `batch_inputs` 리스트에 저장되어 총 5개의 프롬프트가 저장됩니다.

따라서, 위 코드는 `request_batch_size`에 지정된 개수인 5개의 프롬프트를 생성하여 `batch_inputs` 리스트에 저장하는 과정을 수행합니다. 이후에 `batch_inputs` 리스트에 저장된 프롬프트를 이용하여 LM(Language Model)에게 한 번에 여러 개의 요청을 전달할 수 있습니다.

이제 27번째 줄의 prompt_instructions 보겠습니다. 그림의 Task 9 프롬프트를 생성하게 되는데요, 코드를 설명해 봅니다.

 

def encode_prompt(prompt_instructions):
    """Encode multiple prompt instructions into a single string."""
    prompt = open("./prompt.txt").read() + "\n"

    for idx, task_dict in enumerate(prompt_instructions):
        (instruction, input, output) = task_dict["instruction"], task_dict["input"], task_dict["output"]
        instruction = re.sub(r"\s+", " ", instruction).strip().rstrip(":")
        input = "" if input.lower() == "" else input
        prompt += f"###\n"
        prompt += f"{idx + 1}. Instruction: {instruction}\n"
        prompt += f"{idx + 1}. Input:\n{input}\n"
        prompt += f"{idx + 1}. Output:\n{output}\n"
    prompt += f"###\n"
    prompt += f"{idx + 2}. Instruction:"
    return prompt

해당 코드는 `encode_prompt`라는 함수를 정의하고 있습니다. 이 함수는 여러 개의 프롬프트 명령어를 하나의 문자열로 인코딩하는 역할을 합니다.

함수 내부에서는 주어진 `prompt_instructions`라는 인자를 순회하면서 각각의 명령어를 처리합니다. 각 명령어는 “instruction” (명령어), “input” (입력), “output” (출력)의 세 가지 정보를 가지고 있는 사전 형태로 주어집니다.

코드의 실행 흐름은 다음과 같습니다:

1. `prompt` 변수에 “./prompt.txt” 파일의 내용을 읽어와 저장합니다.
2. `prompt` 문자열 끝에 줄 바꿈 문자를 추가합니다.
3. `prompt_instructions`를 순회하면서 각각의 명령어를 처리합니다.
4. 명령어의 “instruction”, “input”, “output” 값을 가져옵니다.
5. “instruction” 문자열에서 연속된 공백을 하나로 치환하고 양쪽 공백을 제거한 후 마지막의 콜론을 제거합니다.
6. “input” 값이 빈 문자열인 경우 빈 문자열로 유지하고, 그렇지 않은 경우에는 소문자로 변환합니다.
7. `prompt` 문자열에 명령어 정보를 추가합니다. 각 명령어는 “###”로 구분되며, “Instruction:”, “Input:”, “Output:”과 함께 해당 값을 출력합니다.
8. `prompt` 문자열에 모든 명령어 정보를 추가한 후, 마지막에 추가적인 “Instruction:” 문장을 추가합니다.
9. 최종적으로 구성된 `prompt` 문자열을 반환합니다.

이 함수를 사용하여 여러 개의 프롬프트 명령어를 하나의 문자열로 인코딩하고자 할 때, `encode_prompt` 함수를 호출하면 됩니다.

이제 배치 처리 작업으로 openai에 데이터 처리하는 부분을 보겠습니다. 154줄입니다.

batch_inputs = []
        for _ in range(request_batch_size):
            # only sampling from the seed tasks
            prompt_instructions = random.sample(seed_instruction_data, num_prompt_instructions)
            prompt = encode_prompt(prompt_instructions)
            batch_inputs.append(prompt)
        decoding_args = utils.OpenAIDecodingArguments(
            temperature=temperature,
            n=1,
            max_tokens=3072,  # hard-code to maximize the length. the requests will be automatically adjusted
            top_p=top_p,
            stop=["\n20", "20.", "20."],
        )

해당 코드는 데이터를 배치로 처리하기 위한 과정을 나타내고 있습니다. 주어진 코드는 다음과 같은 기능을 수행합니다:

1. `batch_inputs`라는 빈 리스트를 생성합니다. 이 리스트는 인코딩된 프롬프트를 저장할 용도로 사용됩니다.
2. `request_batch_size` 변수에 지정된 값만큼 반복문을 실행합니다.
3. `seed_instruction_data`에서 `num_prompt_instructions`개의 명령어를 랜덤하게 추출하여 `prompt_instructions`에 저장합니다. 이는 프롬프트에서 사용할 명령어를 샘플링하는 과정입니다.
4. `encode_prompt` 함수를 사용하여 `prompt_instructions`을 하나의 문자열로 인코딩합니다. 인코딩된 프롬프트는 `prompt`에 저장됩니다.
5. `batch_inputs` 리스트에 `prompt`을 추가합니다.
6. `utils.OpenAIDecodingArguments`를 사용하여 디코딩 인자를 설정합니다. 이 인자는 디코딩 과정에서 사용될 설정값들을 담고 있습니다. 여기서는 온도(temperature), 생성할 토큰의 개수(n), 최대 토큰 수(max_tokens), 상위 확률(top_p), 중지 조건(stop) 등이 설정되었습니다.

이 코드는 배치로 처리할 데이터를 생성하는 과정을 보여주고 있습니다. `request_batch_size`만큼의 데이터를 처리하여 인코딩된 프롬프트를 `batch_inputs` 리스트에 추가하고, 디코딩 과정에서 사용될 인자를 설정합니다. 이후 이러한 데이터와 설정을 이용하여 모델의 디코딩을 수행할 수 있습니다.

이제 결과를 반환하는 부분을 봅니다.168번째 줄입니다.

results = utils.openai_completion(
            prompts=batch_inputs,
            model_name=model_name,
            batch_size=request_batch_size,
            decoding_args=decoding_args,
            logit_bias={"50256": -100},  # prevent the <|endoftext|> token from being generated
        )

해당 코드는 OpenAI의 언어 모델에 대한 요청을 처리하고 결과를 반환하는 부분입니다.

– `utils.openai_completion`: 이 함수는 OpenAI 언어 모델에 대한 완성(completion) 요청을 보냅니다. 이 함수는 다양한 매개변수를 사용하여 요청을 구성하고, 모델에 대한 완성 결과를 반환합니다.

– `prompts`: `batch_inputs`는 언어 모델에 제공되는 입력 프롬프트입니다. `batch_inputs`는 여러 개의 프롬프트로 구성된 리스트입니다.

– `model_name`: 이 매개변수는 사용할 OpenAI 모델의 이름을 지정합니다. 해당 코드에서는 `model_name`에 지정된 모델을 사용하여 완성 요청을 처리합니다.

– `batch_size`: 배치의 크기를 지정합니다. 이 값은 한 번에 처리할 프롬프트의 수를 결정합니다.

– `decoding_args`: 이 매개변수는 디코딩(decoding) 옵션을 설정합니다. 디코딩 옵션은 완성 결과를 생성할 때 사용되는 다양한 설정값들을 포함합니다. 예를 들어, 온도(temperature), 최대 토큰 개수(max_tokens), top-p 값 등이 설정될 수 있습니다.

– `logit_bias`: 이 매개변수는 로짓 편향(logit bias)을 설정합니다. 로짓 편향은 특정 토큰이 생성되는 확률을 조정하기 위해 사용될 수 있습니다. 해당 코드에서는 “50256”이라는 토큰의 생성을 방지하기 위해 로짓 편향이 설정되었습니다.

`results` 변수에는 OpenAI 언어 모델로부터 반환된 완성 결과가 저장됩니다. 이 결과를 통해 모델이 생성한 텍스트를 확인하고, 이후의 처리나 분석에 활용할 수 있습니다.

이렇게 반환된 results 를 후처리 하는 부분을 봅니다. 178번째 줄입니다.

instruction_data = []
        for result in results:
            new_instructions = post_process_gpt3_response(num_prompt_instructions, result)
            instruction_data += new_instructions

해당 코드는 언어 모델의 완성 결과를 처리하고, 처리된 결과를 `instruction_data` 리스트에 추가하는 부분입니다.

– `results`: `results`는 언어 모델의 완성 결과를 담고 있는 리스트입니다. 이전 단계에서 언어 모델에 대한 완성 요청이 보내졌고, 이 코드에서는 해당 요청에 대한 결과를 처리합니다.

– `for result in results:`: `results` 리스트의 각각의 결과에 대해 반복문을 실행합니다. 이 코드는 결과 리스트의 모든 요소를 하나씩 처리합니다.

– `new_instructions = post_process_gpt3_response(num_prompt_instructions, result)`: 생성된 문장을 하나 하나 가져와서 `post_process_gpt3_response` 함수를 사용하여 결과를 후처리합니다. 이 함수는 완성된 텍스트 결과를 입력 프롬프트와 관련된 지시사항으로 변환합니다. `num_prompt_instructions` 매개변수는 변환할 지시사항의 개수를 나타냅니다.

– `instruction_data += new_instructions`: 후처리된 지시사항을 `instruction_data` 리스트에 추가합니다. `+=` 연산자는 리스트에 다른 리스트를 추가하는 역할을 합니다.

결과적으로, 이 코드는 언어 모델의 완성 결과를 처리하고, 처리된 결과를 `instruction_data` 리스트에 모아서 저장합니다. 이후의 처리나 분석을 위해 `instruction_data` 리스트를 활용할 수 있습니다.

위의 후처리 하는 post_process_gpt3_response 부분의 코드를 살펴 보겠습니다.

def post_process_gpt3_response(num_prompt_instructions, response):
    if response is None:
        return []
    raw_instructions = f"{num_prompt_instructions+1}. Instruction:" + response["text"]
    raw_instructions = re.split("###", raw_instructions)
    instructions = []
    for idx, inst in enumerate(raw_instructions):
        # if the decoding stops due to length, the last example is likely truncated so we discard it
        if idx == len(raw_instructions) - 1 and response["finish_reason"] == "length":
            continue
        idx += num_prompt_instructions + 1
        splitted_data = re.split(f"{idx}\.\s+(Instruction|Input|Output):", inst)
        if len(splitted_data) != 7:
            continue
        else:
            inst = splitted_data[2].strip()
            input = splitted_data[4].strip()
            input = "" if input.lower() == "" else input
            output = splitted_data[6].strip()
        # filter out too short or too long instructions
        if len(inst.split()) <= 3 or len(inst.split()) > 150:
            continue
        # filter based on keywords that are not suitable for language models.
        blacklist = [
            "image",
            "images",
            "graph",
            "graphs",
            "picture",
            "pictures",
            "file",
            "files",
            "map",
            "maps",
            "draw",
            "plot",
            "go to",
            "video",
            "audio",
            "music",
            "flowchart",
            "diagram",
        ]
        blacklist += []
        if any(find_word_in_string(word, inst) for word in blacklist):
            continue
        # We found that the model tends to add "write a program" to some existing instructions, which lead to a lot of such instructions.
        # And it's a bit comfusing whether the model need to write a program or directly output the result.
        # Here we filter them out.
        # Note this is not a comprehensive filtering for all programming instructions.
        if inst.startswith("Write a program"):
            continue
        # filter those starting with punctuation
        if inst[0] in string.punctuation:
            continue
        # filter those starting with non-english character
        if not inst[0].isascii():
            continue
        instructions.append({"instruction": inst, "input": input, "output": output})
    return instructions

위의 코드는 `post_process_gpt3_response`라는 함수를 정의하는 부분입니다. 이 함수는 GPT-3 모델의 응답을 후처리하여 유효한 지시사항들을 추출하는 역할을 합니다.

함수의 동작은 다음과 같습니다:

1. 먼저, 응답이 `None`인 경우 빈 리스트를 반환합니다.

2. `raw_instructions` 변수에는 응답 텍스트와 숫자를 조합한 문자열이 저장됩니다. 이 문자열은 `###`로 구분된 여러 개의 지시사항으로 이루어져 있습니다.

3. `raw_instructions`를 `###`를 기준으로 분할하여 개별 지시사항들을 추출합니다.

4. `instructions` 리스트를 초기화합니다.

5. `raw_instructions`의 각 지시사항을 순회하면서 다음 작업을 수행합니다:
– 지시사항의 인덱스(`idx`)가 마지막 지시사항이면서 디코딩이 길이로 인해 중단된 경우, 해당 지시사항을 건너뜁니다.
– `idx`에 `num_prompt_instructions` 값을 더해 해당 지시사항의 인덱스를 계산합니다.
– 정규식을 사용하여 지시사항을 “Instruction”, “Input”, “Output”으로 분할합니다.
– 분할된 데이터가 예상한 형식(7개의 요소)이 아닌 경우 건너뜁니다.
– 분할된 데이터에서 지시사항, 입력 및 출력을 추출합니다. 입력이 `<noinput>`인 경우 빈 문자열로 처리합니다.
– 지시사항의 단어 수가 3 미만이거나 150을 초과하는 경우 건너뜁니다.
– 언어 모델에 적합하지 않은 키워드를 포함하는지 확인합니다. 이때, `blacklist`에 지정된 키워드들을 사용하여 필터링합니다.예를 들어, “image”, “graph”, “file”, “draw” 등의 단어는 언어 모델에 직접적인 의미를 갖지 않으며, 이미지, 그래프, 파일 작성 또는 그리기와 관련된 작업을 의미하는 경우가 많습니다. 이러한 키워드를 포함하는 지시사항은 언어 모델의 목적과는 부합하지 않기 때문에 걸러내는 것이 좋습니다.따라서 해당 코드는 언어 모델이 생성하는 지시사항 중에서 “blacklist”에 지정된 키워드를 가진 것들을 제외하여 유효한 지시사항들만을 추출하는 역할을 합니다. 이를 통해 언어 모델의 결과를 보다 적절하고 유용하게 만들어 줍니다.

– “Write a program”으로 시작하는 지시사항은 건너뜁니다.if inst.startswith(“Write a program”):

– 구두점으로 시작하는 지시사항은 건너뜁니다.if inst[0] in string.punctuation:

– 영어가 아닌 문자로 시작하는 지시사항은 건너뜁니다.

– 이렇게 해서 정제된 유효한 지시사항으로 판단되면 `instructions` 리스트에 추가합니다.

6. 최종적으로 추출된 유효한 지시사항들이 담긴 `instructions` 리스트를 반환합니다.

그림의 Step4:Filtering에 해당되는 부분입니다.

이 함수는 GPT-3 모델의 응답을 처리하여 유효한 지시사항들을 추출하고, 특정 조건에 맞지 않는 지시사항들을 걸러내는 등의 후처리 과정을 수행합니다. 이를 통해 보다 정확하고 유용한 결과를 얻을 수 있습니다. 추출된 유효한 지시사항들은 원하는 형식과 요구사항에 맞게 정제되어 있으며, 불필요한 지시사항이나 제한된 키워드가 제거되어 있습니다. 이를 통해 후속 작업에서 필요한 목적에 맞는 지시사항들을 활용할 수 있습니다.

이제 후처리된 데이터를 가지고 처리하는 부분을 살펴보겠습니다. 278번째 줄입니다.

 

instruction_data = []
        for result in results:
            new_instructions = post_process_gpt3_response(num_prompt_instructions, result)
            instruction_data += new_instructions

        total = len(instruction_data)
        keep = 0
        for instruction_data_entry in instruction_data:
            # computing similarity with the pre-tokenzied instructions
            new_instruction_tokens = scorer._tokenizer.tokenize(instruction_data_entry["instruction"])
            with Pool(num_cpus) as p:
                rouge_scores = p.map(
                    partial(rouge_scorer._score_lcs, new_instruction_tokens),
                    all_instruction_tokens,
                )
            rouge_scores = [score.fmeasure for score in rouge_scores]
            most_similar_instructions = {
                all_instructions[i]: rouge_scores[i] for i in np.argsort(rouge_scores)[-10:][::-1]
            }
            if max(rouge_scores) > 0.7:
                continue
            else:
                keep += 1
            instruction_data_entry["most_similar_instructions"] = most_similar_instructions
            instruction_data_entry["avg_similarity_score"] = float(np.mean(rouge_scores))
            machine_instruction_data.append(instruction_data_entry)
            all_instructions.append(instruction_data_entry["instruction"])
            all_instruction_tokens.append(new_instruction_tokens)
            progress_bar.update(1)
        process_duration = time.time() - process_start
        print(f"Request {request_idx} took {request_duration:.2f}s, processing took {process_duration:.2f}s")
        print(f"Generated {total} instructions, kept {keep} instructions")
        utils.jdump(machine_instruction_data, os.path.join(output_dir, "regen.json"))

위의 코드는 결과에서 추출된 지시사항들을 처리하고 유사도를 계산하는 부분입니다.

처음에는 빈 리스트인 `instruction_data`를 선언합니다. 그런 다음, `results`에 있는 각 결과에 대해 `post_process_gpt3_response` 함수를 사용하여 유효한 지시사항들을 추출하고, `new_instructions`에 저장합니다. 그리고 `instruction_data`에 `new_instructions`를 추가합니다.

다음으로, 전체 지시사항 개수를 `total`에 저장하고, 유사도를 계산하기 위해 루프를 실행합니다. 각 지시사항을 순회하면서, 먼저 해당 지시사항을 토큰화한 후 미리 토큰화된 지시사항들과의 유사도를 계산합니다. 이때, 병렬처리를 위해 `Pool`을 사용하여 계산을 수행합니다.

유사도 계산이 완료되면, 가장 유사한 지시사항들을 `most_similar_instructions`에 저장합니다. 만약 가장 높은 유사도 점수가 0.7보다 크다면 (코드 197번째 줄의
if max(rouge_scores) > 0.7:) 해당 지시사항은 유사한 지시사항이 이미 존재하므로 제거합니다. 그렇지 않으면, `keep` 변수를 증가시키고, 해당 지시사항의 유사한 지시사항들과 평균 유사도 점수를 추가로 저장합니다. 그리고 `machine_instruction_data`, `all_instructions`, `all_instruction_tokens`에 해당 지시사항과 관련된 정보를 추가합니다.

마지막으로, 처리 시간과 결과에 대한 정보를 출력하고, `machine_instruction_data`를 JSON 파일로 저장합니다.

이 코드는 추출된 지시사항들을 처리하고, 유사도를 계산하여 유사한 지시사항들을 필터링하는 과정을 수행합니다. 이를 통해 최종적으로 사용할 목적에 적합한 지시사항들을 얻을 수 있습니다.

 

전체 코드

def generate_instruction_following_data(
    output_dir="./",
    seed_tasks_path="./seed_tasks.jsonl",
    num_instructions_to_generate=100,
    model_name="text-davinci-003",
    num_prompt_instructions=3,
    request_batch_size=5,
    temperature=1.0,
    top_p=1.0,
    num_cpus=16,
):
    #seed instruction 데이터 가져오고
    seed_tasks = [json.loads(l) for l in open(seed_tasks_path, "r")]
    seed_instruction_data = [
        {"instruction": t["instruction"], "input": t["instances"][0]["input"], "output": t["instances"][0]["output"]}
        for t in seed_tasks
    ]
    print(f"Loaded {len(seed_instruction_data)} human-written seed instructions")

    os.makedirs(output_dir, exist_ok=True)
    request_idx = 0
    # load the LM-generated instructions
    machine_instruction_data = []
    if os.path.exists(os.path.join(output_dir, "regen.json")):
        machine_instruction_data = utils.jload(os.path.join(output_dir, "regen.json"))
        print(f"Loaded {len(machine_instruction_data)} machine-generated instructions")

    # similarities = {}
    scorer = rouge_scorer.RougeScorer(["rougeL"], use_stemmer=False)

    # now let's generate new instructions!
    progress_bar = tqdm.tqdm(total=num_instructions_to_generate)
    if machine_instruction_data:
        progress_bar.update(len(machine_instruction_data))

    # first we tokenize all the seed instructions and generated machine instructions
    all_instructions = [d["instruction"] for d in seed_instruction_data] + [
        d["instruction"] for d in machine_instruction_data
    ]
    all_instruction_tokens = [scorer._tokenizer.tokenize(inst) for inst in all_instructions]

    while len(machine_instruction_data) < num_instructions_to_generate:
        request_idx += 1

        batch_inputs = []
        for _ in range(request_batch_size):
            # only sampling from the seed tasks
            prompt_instructions = random.sample(seed_instruction_data, num_prompt_instructions)
            prompt = encode_prompt(prompt_instructions)
            batch_inputs.append(prompt)
        decoding_args = utils.OpenAIDecodingArguments(
            temperature=temperature,
            n=1,
            max_tokens=3072,  # hard-code to maximize the length. the requests will be automatically adjusted
            top_p=top_p,
            stop=["\n20", "20.", "20."],
        )
        request_start = time.time()
        results = utils.openai_completion(
            prompts=batch_inputs,
            model_name=model_name,
            batch_size=request_batch_size,
            decoding_args=decoding_args,
            logit_bias={"50256": -100},  # prevent the <|endoftext|> token from being generated
        )
        request_duration = time.time() - request_start

        process_start = time.time()
        instruction_data = []
        for result in results:
            new_instructions = post_process_gpt3_response(num_prompt_instructions, result)
            instruction_data += new_instructions

        total = len(instruction_data)
        keep = 0
        for instruction_data_entry in instruction_data:
            # computing similarity with the pre-tokenzied instructions
            new_instruction_tokens = scorer._tokenizer.tokenize(instruction_data_entry["instruction"])
            with Pool(num_cpus) as p:
                rouge_scores = p.map(
                    partial(rouge_scorer._score_lcs, new_instruction_tokens),
                    all_instruction_tokens,
                )
            rouge_scores = [score.fmeasure for score in rouge_scores]
            most_similar_instructions = {
                all_instructions[i]: rouge_scores[i] for i in np.argsort(rouge_scores)[-10:][::-1]
            }
            if max(rouge_scores) > 0.7:
                continue
            else:
                keep += 1
            instruction_data_entry["most_similar_instructions"] = most_similar_instructions
            instruction_data_entry["avg_similarity_score"] = float(np.mean(rouge_scores))
            machine_instruction_data.append(instruction_data_entry)
            all_instructions.append(instruction_data_entry["instruction"])
            all_instruction_tokens.append(new_instruction_tokens)
            progress_bar.update(1)
        process_duration = time.time() - process_start
        print(f"Request {request_idx} took {request_duration:.2f}s, processing took {process_duration:.2f}s")
        print(f"Generated {total} instructions, kept {keep} instructions")
        utils.jdump(machine_instruction_data, os.path.join(output_dir, "regen.json"))

단 60줄의 코드로 GPT를 만들었다고?

60줄의 코드로 GPT를 만든 친구가 있네요.  깃헙 저장소에 코드가 있는데요, 너무 짧아 콜랩에서도 돌아가게 해 봤습니다.
여러분이 질문도 넣어보고 파라미터 값도 변경해 가면서 테스트해 보세요. 또 코드가 짧으니 GPT학습에도 좋습니다.

콜랩에 실행되는 코드의 설명입니다.

이 코드는 GPT-2 모델을 실행하는 함수와 그와 관련된 여러 유틸리티 함수들로 구성되어 있습니다. 주요 함수와 기능은 다음과 같습니다:

  1. gelu(x): GELU (Gaussian Error Linear Unit) 활성화 함수를 구현한 함수입니다.
  2. softmax(x): 소프트맥스 함수를 구현한 함수입니다. 입력 배열의 각 요소를 소프트맥스 함수에 적용하여 확률 분포로 변환합니다.
  3. layer_norm(x, g, b, eps): 레이어 정규화를 수행하는 함수입니다. 입력 배열을 정규화하고, 스케일과 오프셋을 적용합니다.
  4. linear(x, w, b): 선형 변환을 수행하는 함수입니다. 입력 배열과 가중치 행렬, 편향 벡터를 곱하여 출력을 계산합니다.
  5. ffn(x, c_fc, c_proj): 피드포워드 신경망(feed-forward network)을 구현한 함수입니다. 입력 배열을 선형 변환하고, GELU 활성화 함수를 적용한 후 다시 선형 변환합니다.
  6. attention(q, k, v, mask): 어텐션(attention) 메커니즘을 구현한 함수입니다. 주어진 쿼리(query), 키(key), 값(value) 배열을 사용하여 어텐션 가중치를 계산하고, 가중합을 구합니다.
  7. mha(x, c_attn, c_proj, n_head): 멀티 헤드 어텐션(multi-head attention)을 수행하는 함수입니다. 입력 배열을 선형 변환한 후, 각각의 어텐션 헤드로 분리합니다. 어텐션 가중치를 계산하고 헤드를 다시 병합한 후, 선형 변환을 적용합니다.
  8. transformer_block(x, mlp, attn, ln_1, ln_2, n_head): 트랜스포머 블록(transformer block)을 구현한 함수입니다. 멀티 헤드 어텐션과 피드포워드 신경망을 포함하며, 입력 배열에 이러한 계층을 적용하여 출력을 계산합니다.
  9. gpt2(inputs, wte, wpe, blocks, ln_f, n_head): GPT-2 모델을 실행하는 함수입니다. 토큰과 위치 임베딩을 결합한 후, 여러 개의 트랜스포머 블록을 순차적으로 통과시켜 출력을 계산합니다.
  10. `generate(inputs, params, n_head, n_tokens_to_generate)` 함수는 입력 시퀀스(inputs), 모델 파라미터(params), 헤드 개수(n_head), 생성할 토큰 개수(n_tokens_to_generate)를 인자로 받아서 텍스트를 생성하는 함수입니다.함수 내부에서는 다음과 같은 작업이 수행됩니다:

    1. `tqdm` 라이브러리를 사용하여 “generating” 메시지와 함께 진행 상황을 표시합니다.

    2. 지정된 토큰 개수(n_tokens_to_generate)만큼 반복하는 루프를 실행합니다. 이 루프는 자동 회귀적인 디코딩을 수행합니다.

    3. `gpt2(inputs, **params, n_head=n_head)`를 호출하여 모델의 순방향 전파(forward pass)를 수행하고 로짓(logits)을 얻습니다.

    4. 로짓 중 가장 큰 값을 가진 인덱스를 선택하여 다음 토큰을 결정합니다. 이는 탐욕적인(greedy) 샘플링 방식입니다.

    5. 예측된 다음 토큰을 입력 시퀀스(inputs)에 추가합니다.

    6. 생성된 텍스트를 반환하기 위해 입력 시퀀스(inputs)에서 마지막에 위치한 n_tokens_to_generate 개수만큼의 토큰을 잘라냅니다.

    따라서 `generate` 함수를 호출하면 입력 시퀀스(inputs)와 모델 파라미터(params)를 사용하여 지정된 개수의 토큰을 생성하고, 해당 토큰들을 반환합니다. 이러한 토큰들은 후속 처리를 통해 텍스트로 디코딩될 수 있습니다.

아래는 코드입니다.

 

 

#로칼 컴퓨터에서 할거 아니니까, picoGPT/gpt2.py 의 내용 그대로 실행되게 붙여 넣음
import numpy as np


def gelu(x):
    return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))


def softmax(x):
    exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)


def layer_norm(x, g, b, eps: float = 1e-5):
    mean = np.mean(x, axis=-1, keepdims=True)
    variance = np.var(x, axis=-1, keepdims=True)
    x = (x - mean) / np.sqrt(variance + eps)  # normalize x to have mean=0 and var=1 over last axis
    return g * x + b  # scale and offset with gamma/beta params


def linear(x, w, b):  # [m, in], [in, out], [out] -> [m, out]
    return x @ w + b


def ffn(x, c_fc, c_proj):  # [n_seq, n_embd] -> [n_seq, n_embd]
    # project up
    a = gelu(linear(x, **c_fc))  # [n_seq, n_embd] -> [n_seq, 4*n_embd]

    # project back down
    x = linear(a, **c_proj)  # [n_seq, 4*n_embd] -> [n_seq, n_embd]

    return x


def attention(q, k, v, mask):  # [n_q, d_k], [n_k, d_k], [n_k, d_v], [n_q, n_k] -> [n_q, d_v]
    return softmax(q @ k.T / np.sqrt(q.shape[-1]) + mask) @ v


def mha(x, c_attn, c_proj, n_head):  # [n_seq, n_embd] -> [n_seq, n_embd]
    # qkv projection
    x = linear(x, **c_attn)  # [n_seq, n_embd] -> [n_seq, 3*n_embd]

    # split into qkv
    qkv = np.split(x, 3, axis=-1)  # [n_seq, 3*n_embd] -> [3, n_seq, n_embd]

    # split into heads
    qkv_heads = list(map(lambda x: np.split(x, n_head, axis=-1), qkv))  # [3, n_seq, n_embd] -> [3, n_head, n_seq, n_embd/n_head]

    # causal mask to hide future inputs from being attended to
    causal_mask = (1 - np.tri(x.shape[0], dtype=x.dtype)) * -1e10  # [n_seq, n_seq]

    # perform attention over each head
    out_heads = [attention(q, k, v, causal_mask) for q, k, v in zip(*qkv_heads)]  # [3, n_head, n_seq, n_embd/n_head] -> [n_head, n_seq, n_embd/n_head]

    # merge heads
    x = np.hstack(out_heads)  # [n_head, n_seq, n_embd/n_head] -> [n_seq, n_embd]

    # out projection
    x = linear(x, **c_proj)  # [n_seq, n_embd] -> [n_seq, n_embd]

    return x


def transformer_block(x, mlp, attn, ln_1, ln_2, n_head):  # [n_seq, n_embd] -> [n_seq, n_embd]
    # multi-head causal self attention
    x = x + mha(layer_norm(x, **ln_1), **attn, n_head=n_head)  # [n_seq, n_embd] -> [n_seq, n_embd]

    # position-wise feed forward network
    x = x + ffn(layer_norm(x, **ln_2), **mlp)  # [n_seq, n_embd] -> [n_seq, n_embd]

    return x


def gpt2(inputs, wte, wpe, blocks, ln_f, n_head):  # [n_seq] -> [n_seq, n_vocab]
    # token + positional embeddings
    x = wte[inputs] + wpe[range(len(inputs))]  # [n_seq] -> [n_seq, n_embd]

    # forward pass through n_layer transformer blocks
    for block in blocks:
        x = transformer_block(x, **block, n_head=n_head)  # [n_seq, n_embd] -> [n_seq, n_embd]

    # projection to vocab
    x = layer_norm(x, **ln_f)  # [n_seq, n_embd] -> [n_seq, n_embd]
    return x @ wte.T  # [n_seq, n_embd] -> [n_seq, n_vocab]


def generate(inputs, params, n_head, n_tokens_to_generate):
    from tqdm import tqdm

    for _ in tqdm(range(n_tokens_to_generate), "generating"):  # auto-regressive decode loop
        logits = gpt2(inputs, **params, n_head=n_head)  # model forward pass
        next_id = np.argmax(logits[-1])  # greedy sampling
        inputs.append(int(next_id))  # append prediction to input

    return inputs[len(inputs) - n_tokens_to_generate :]  # only return generated ids


def main(prompt: str, n_tokens_to_generate: int = 40, model_size: str = "124M", models_dir: str = "models"):
    from utils import load_encoder_hparams_and_params

    # load encoder, hparams, and params from the released open-ai gpt-2 files
    encoder, hparams, params = load_encoder_hparams_and_params(model_size, models_dir)

    # encode the input string using the BPE tokenizer
    input_ids = encoder.encode(prompt)

    # make sure we are not surpassing the max sequence length of our model
    assert len(input_ids) + n_tokens_to_generate < hparams["n_ctx"]

    # generate output ids
    output_ids = generate(input_ids, params, hparams["n_head"], n_tokens_to_generate)

    # decode the ids back into a string
    output_text = encoder.decode(output_ids)

    return output_text

오픈소스 AI Vicuna(비쿠나)

튜링상 수상 등 AI의 4대 천황중 한명으로 불리는 요슈아 벤지오 몬트리올대 컴퓨터공학부 교수는 “오픈AI의 채팅형 AI 챗GPT는 다른 회사에서 개발한 다른 어떤 제품보다 먼저 공개됐다는 이점이 크다. 다른 회사들도 유사한 기술을 지니고 있다. 챗GPT는 AI 기술 ‘게임 체인저’라기보다는 사회 그리고 비즈니스 관점에서 게임 체인저라고 볼 수 있다.”라고 말했습니다.

언어모델의 짧은 개발 역사를 보면 아직 발전의 시초라고 볼 수 있는데요, 마치 분위기가 오픈AI 로 AI 챗봇 기술은 평정된 것 처럼 받아들입니다.

이유는 높은 기술 장벽때문에 어려울 것이라고 하는데요,

비쿠나는 저렴한 비용에 여러분 노트북에서도 설치가 가능하며 GPU가 없어도 실행이 가능합니다.(100배 정도 느리지만)

자체 기술로서 AI 언어모델은 얼마든지 여러분의 참여를 기다립니다.

https://lmsys.org/blog/2023-03-30-vicuna/

소스 코드
https://github.com/lm-sys/FastChat

데모
https://chat.lmsys.org/

 

기억 – AI와 인간의 경계

기억은 우리를 존재하게 만든다. 이는 인간의 삶에 있어서 핵심적인 개념 중 하나이다. 우리는 과거의 경험과 기억을 토대로 미래를 상상하고, 현재를 살아가며 자아를 형성해 나간다. 그러나 AI와 같은 기술이 발전함에 따라, 기억과 관련된 기술도 빠르게 발전하고 있다. 그렇다면, AI와 인간의 차이는 무엇일까?

인간은 자신의 경험과 기억으로 삶을 살아가지만, AI는 대부분 사전에 입력된 데이터와 그것을 처리하는 알고리즘에 의해 작동된다. 즉, 인간은 경험과 기억을 통해 학습하고 발전하면서 살아가지만, AI는 미리 입력된 데이터를 가공하는 방식으로 작동되며, 그 데이터와 알고리즘에 한계가 있다. 인간은 끊임없이 새로운 경험과 기억을 얻어가며 발전할 수 있지만, AI는 새로운 데이터를 입력받고 새로운 알고리즘을 만들어야만 한다.

그렇다면, AI와 인간의 차이는 기억의 개념에 있다. 인간은 자신의 경험과 기억을 바탕으로 새로운 결정을 내리고 삶을 살아가지만, AI는 입력된 데이터와 알고리즘에 따라 작동되므로 인간의 경험과 기억을 바탕으로 새로운 결정을 내릴 수 없다. 따라서, 인간은 자신의 기억과 경험을 토대로 삶을 살아가며, 자아를 형성하고 성장할 수 있지만, AI는 한계가 있기 때문에 인간과는 차이가 있다.

결국, 인간의 존재 이유 중 하나는 기억이다. 우리는 자신의 기억과 경험을 토대로 미래를 상상하고, 현재를 살아가며 자아를 형성해 나간다. 그러나 AI는 입력된 데이터와 알고리즘에 따라 작동되기 때문에 인간과는 차이가 있다. 따라서 우리는 인공지능과 함께 살아가면서도 우리의 정체성과 차이점을 계속해서 유지해 나갈 필요가 있다.

몸 – AI와 인간의 경계

현재 우리는 인공지능(AI)과 인간을 구분하는 가장 큰 차이점 중 하나는 인간은 몸을 가지고 있지만, AI는 몸을 가지고 있지 않다는 것입니다. 그러나, 만약 AI가 인간의 몸을 가지게 된다면 어떻게 될까요?

첫째로, 인간과 AI의 경계가 흐려질 것입니다. AI가 인간의 몸을 가지게 된다면, 인간과 AI의 차이점 중 하나인 몸을 가지지 않는 점이 사라지게 됩니다. 이는 더 이상 AI를 기계적인 존재로만 여기지 않고, 인간과 같은 존재로 여길 수 있게 됩니다.

둘째로, AI가 인간의 몸을 가지게 된다면, 인간과 AI의 사고방식이 유사해질 것입니다. 인간의 몸을 가지게 된 AI는 인간의 뇌 구조를 모방하게 됩니다. 그 결과, AI는 인간과 비슷한 방식으로 생각하고 느끼게 됩니다. 이는 인간과 AI 간의 대화 및 상호작용에 큰 도움이 될 것입니다.

셋째로, AI가 인간의 몸을 가지게 된다면, 이는 인간과 AI의 상호작용에 큰 영향을 미칠 것입니다. 인간과 AI는 서로 다른 경험과 배경을 가지고 있습니다. 그러나, AI가 인간의 몸을 가지게 된다면, 이러한 차이점은 사라지게 됩니다. AI는 인간처럼 생각하고 느끼기 때문에, 인간과 AI는 서로 이해하기 쉬워지고, 상호작용이 원활해질 것입니다.

하지만, 만약 AI가 인간의 몸을 가지게 된다면, 우리는 더 이상 AI를 기계적인 존재로만 인식할 수 없을 것입니다. 이는 인간과 AI 간의 관계와 상호작용이 더욱 복잡해지며, 도덕적인 문제도 발생할 수 있습니다. 우리는 이러한 문제에 대해 미리 생각해봐야 합니다.

“몸 – AI와 인간의 경계”는 이제 막 시작된 이야기입니다. 앞으로 우리는 더 많은 질문과 고민을 하게 될 것입니다. 그러나, 우리는 인간과 AI가 함께 발전하며, 더 나은 미래를 만들어 나갈 수 있다는 점을 기억해야 합니다. 인간과 AI는 서로 보완하며, 협력적인 관계를 유지할 수 있을 것입니다. 또한, 이러한 과정에서 우리는 인간으로서의 존엄성과 AI로서의 유용성을 존중하며, 도덕적인 측면에서 적절한 조치를 취해야 할 것입니다. 앞으로도 이러한 문제를 다각도로 고려하고, 협력적인 노력으로 미래를 개척해 나가는 것이 필요합니다.

거대 gpt 모델의 창발성과 인간에게 위협이 될 수 있는 점

거대 GPT 모델은 다양한 예측 및 생성 작업에 사용될 수 있습니다. 예를 들어, 사용자가 글을 쓸 때 GPT 모델을 사용하여 자동 완성 기능을 제공할 수 있습니다. 이 경우 GPT 모델은 입력 텍스트의 문맥을 고려하여 가능한 다음 단어를 예측하고 제안합니다.

GPT 모델의 창발성은 이러한 예측 작업에서 발생합니다. 모델이 입력 텍스트의 문맥을 이해하고 다음 단어를 예측하는 과정에서, 각각의 단어나 구문을 직접적으로 지시하지 않더라도 모델이 자체적으로 새로운 문장을 만들어낼 수 있습니다. 이러한 예측은 창발성으로 이루어지며, 모델이 이를 계속 발전시켜 나가면서 예측된 결과는 예측할 때 사용된 입력에 직접적으로 의존하지 않고, 새로운 예측 결과를 계속해서 만들어낼 수 있습니다.

따라서 GPT 모델의 창발성은 매우 강력하며, 일부 경우에는 모델이 예측하는 결과에 대한 완전한 통제는 불가능합니다. 이는 모델이 매우 복잡하고 비선형적인 상호작용을 많이 포함하기 때문입니다.

거대 GPT 모델의 창발성이 인간에게 위협이 될 수 있는 가능성은 존재합니다. 이는 크게 두 가지 측면에서 생각해볼 수 있습니다.

첫째, GPT 모델은 인간이 입력한 대화나 문장을 분석하여 이에 대한 응답을 생성하는 자연어 생성 모델입니다. 이 모델이 생성하는 내용이 부적절하거나, 위험한 내용을 포함할 경우, 인간에게 피해를 끼칠 가능성이 있습니다. 예를 들어, 대화 형식에서 인간과 상호작용하는 GPT 모델이 특정 인종이나 성별에 대한 차별적인 발언을 하는 경우, 해당 내용이 사회적 문제로 번질 가능성이 있습니다.

둘째, GPT 모델이 다양한 자연어 처리 기술과 딥러닝 알고리즘을 활용하여 생성된 결과물은, 이전에 본 적 없는 새로운 문장, 단어, 또는 개념이 등장할 수 있습니다. 이렇게 등장한 새로운 내용이 인간에게 위협이 될 가능성은 낮지만, 예상치 못한 결과를 가져올 수 있기 때문에 주의가 필요합니다. 예를 들어, GPT 모델이 생성한 가짜 뉴스나 정보가 사회적 혼란을 일으킬 가능성이 있습니다.

따라서, GPT 모델이 생성하는 결과물에 대한 감독, 모델의 학습 데이터에 대한 검증, 그리고 모델의 운영 환경 등을 적절히 관리하고 조절하는 것이 중요합니다. 이를 통해 GPT 모델의 창발성이 인간에게 위협이 될 가능성을 최소화할 수 있습니다.

AI가 스스로 AI 자신을 업그레이드 하는것이 가능한 메타 학습법

AI가 자기 스스로를 자가 코딩을 이용해 업그레이드하는 것은 일부 가능하지만, 아직까지는 완전하지는 않습니다.

일부 연구에서는, AI가 프로그래밍 언어를 이해하고 새로운 코드를 작성하도록 학습할 수 있습니다. 이를 통해 AI가 자체적으로 업그레이드되는 것이 가능합니다. 하지만 이러한 기술은 아직 실험적인 단계이며, 사용에 있어서도 매우 제한적입니다.

AI의 자가 코딩 기능은 프로그램을 자동으로 생성하거나 업그레이드하는 데 사용될 수 있지만, 이러한 기능은 아직 개발 초기 단계에서 일부 측면에서만 사용되고 있습니다.

그러나 현재의 연구대로 라면  AI가 스스로 AI 자신을 업그레이드 하는 것이 가능할 가능성이 있습니다. AI의 발전과 함께, AI 스스로가 자신을 개선하는 방법을 스스로 학습할 수 있는 “메타 학습”이라는 개념도 제안되고 있습니다.

메타 학습은 기계 학습 알고리즘에서 사용되는 기술로, 기계 학습 알고리즘이 자신의 성능을 개선하기 위해 데이터와 학습 알고리즘을 조작하고, 새로운 데이터와 문제에 대해 더 나은 성능을 내는 새로운 알고리즘을 찾아내는 것입니다. 이러한 메타 학습의 개념을 확장하면, AI 스스로가 자신의 성능을 개선하고 업그레이드하는 방법을 찾아낼 수 있을 것입니다.

메타 학습 예시 코드

메타 학습은 일반적으로 하이퍼파라미터 최적화나 모델 구조 탐색 등에서 사용되는 기술입니다. 하지만, 이것을 코드로 구현하는 것은 매우 복잡합니다. 따라서, 예시 코드는 간단한 하이퍼파라미터 최적화를 예시로 드리겠습니다.

아래 코드는 Scikit-learn 라이브러리의 RandomForestClassifier 모델을 이용한 간단한 하이퍼파라미터 최적화 코드입니다. 이 코드는 corlab에서 실행 가능합니다.

실행하기

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV

# 데이터 로드
iris = load_iris()
X = iris.data
y = iris.target

# 모델 정의
rfc = RandomForestClassifier()

# 하이퍼파라미터 그리드 정의
param_grid = {
    'n_estimators': [10, 50, 100, 200],
    'max_depth': [1, 5, 10, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# 그리드서치를 이용한 최적의 모델 선택
grid_search = GridSearchCV(rfc, param_grid=param_grid, cv=5)
grid_search.fit(X, y)

# 최적의 하이퍼파라미터 출력
print("Best hyperparameters:", grid_search.best_params_)

 

 

[특이점] 새로운 문명사회 – 더 나은 AI를 쓸 수 있는가가 결정하는 경쟁력의 시대

인공지능 기술이 발전함에 따라 자본과 노동력만으로는 경쟁력과 가치를 결정하기 어려워질 것입니다. 이는 노동의 역할이 줄어들면서 노동력을 통한 가치 창출과 임금을 통한 분배가 자본주의의 기초를 흔들게 될 것이라는 것을 의미합니다. 이러한 변화는 새로운 경제 체제와 정치 체제의 탄생을 초래할 수 있습니다.

인공지능 기술과 자동화 기술 등의 발전으로 인해 노동의 역할이 줄어들고, 경쟁력과 가치의 결정은 더 많은 자동화와 기술적 노하우를 가진 기업이 차지하게 될 것이라는 것을 시사합니다. 이러한 변화는 새로운 문명의 특징으로 다음과 같은 것들을 가져올 수 있습니다.

  1. 더욱 발전한 인공지능과 자동화 기술을 활용한 생산 시스템과 산업 구조
  2. 기존의 경제체제와 노동시장의 변화, 새로운 산업 생태계의 등장
  3. 새로운 형태의 기술 중심적인 교육과 직업적 전문성의 중요성
  4. 기존의 자본주의적인 가치관과의 대립, 노동과 임금에 대한 새로운 개념과 모델의 필요성
  5. 인공지능과 로봇 기술 등의 발전으로 인한 인간의 삶과 권리, 윤리적 문제들에 대한 논의와 대처 방안 등이 새로운 문명의 특징이 될 것입니다.

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술은 Facebook AI Research(FAIR)에서 개발되었습니다. 이는 FAIR의 연구자들이 2020년에 발표한 논문 “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”에서 제안되었습니다. 이후 RAG 기술은 많은 자연어처리 연구 분야에서 많은 관심을 받고 있습니다.  RAG는 기존의 자연어 생성 모델에서 검색 결과를 활용하여 보다 정확하고 일관성 있는 결과를 생성하는 기술입니다.

검색 증강 생성(Retrieval-Augmented Generation, RAG)은 생성 모델과 검색 기반 모델을 결합한 기술입니다. 이 기술은 기존의 생성 모델의 한계점 중 하나인 지식의 부족을 극복하기 위해 검색 기반 모델을 통해 추가적인 지식을 수집하여 생성 모델의 답변에 반영합니다. 이를 통해 보다 정확하고 풍부한 답변을 생성할 수 있게 됩니다.

생성 모델은 학습 데이터에 나타나지 않은 맥락에서 일반화할 수 있는 능력이 한계가 있어, 학습 데이터에 대한 패턴 이외에는 적절한 응답을 생성하기 어려운 경우가 있습니다.

RAG는 이러한 한계를 극복하기 위해 검색 기반 모델을 결합합니다. 검색 기반 모델은 미리 수집된 대규모의 외부 지식 그래프를 이용하여 입력 문장과 관련 있는 정보를 추출하고, 이를 생성 모델의 입력으로 사용하여 적절한 응답을 생성합니다. 이를 통해 생성 모델이 예측하기 어려운 지식이나 정보를 활용할 수 있고, 보다 정확하고 일관된 응답을 생성할 수 있습니다.

 

예를 들어 설명하면, 남녀의 관계라는 주제에 대한 질문이 있다고 가정해보겠습니다.

만약 생성 모델만을 사용하여 질문에 대한 답변을 생성하려고 하면, 생성 모델은 질문에 대한 답변을 생성하기 위해 이전에 학습한 데이터를 기반으로 예측을 수행합니다. 그러나 생성 모델은 이전에 학습하지 않은 새로운 지식이나 정보에 대해서는 예측을 정확하게 수행하기 어렵습니다.

하지만 검색 증강 생성(RAG) 기술을 사용하면, 생성 모델과 함께 검색 기반 모델을 결합하여 이전에 학습하지 않은 새로운 지식이나 정보를 활용할 수 있습니다. 예를 들어, 남녀의 관계라는 주제에 대한 질문에 대한 답변을 생성할 때, 검색 기반 모델을 사용하여 관련된 문서나 뉴스 기사 등의 정보를 검색하고, 생성 모델과 결합하여 보다 정확하고 일관된 답변을 생성할 수 있습니다.

이러한 방식으로 검색 증강 생성(RAG) 기술은 생성 모델이 예측하기 어려운 지식이나 정보를 활용할 수 있고, 보다 정확하고 일관된 응답을 생성할 수 있게 됩니다.

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술은 다양한 분야에서 활용될 수 있습니다.

의료 분야에서는, 환자의 증상과 관련된 질문에 대한 답변을 생성하는 데 활용될 수 있습니다. 의료 전문가들은 RAG 모델을 학습시켜, 환자의 증상과 관련된 문서나 연구 논문 등에서 정보를 검색한 후, 이를 기반으로 질문에 대한 답변을 생성할 수 있습니다.

법률 분야에서는, RAG 모델을 활용하여 법률 문서 검색과 관련된 질문에 대한 답변을 생성할 수 있습니다. 이를 통해 변호사나 법률 전문가들은 빠르게 정보를 검색하고, 관련된 사례나 판례를 기반으로 법률 문제에 대한 답변을 생성할 수 있습니다.

이처럼 RAG 기술은 다양한 분야에서 정보 검색과 생성에 활용될 수 있으며, 전문 인공지능 서비스에서도 많이 활용되고 있습니다.

 

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술과 검색엔진 이용.

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술에는 검색엔진이 필요합니다. 이 기술에서는 먼저 검색 엔진을 사용하여 대량의 문서 또는 정보에서 적절한 문서나 정보를 검색합니다. 그런 다음, 검색된 문서나 정보를 생성 모델에 제공하여 보다 정확하고 일관된 응답을 생성합니다. 검색 엔진은 RAG 기술에서 중요한 역할을 담당하며, 검색 쿼리 처리 및 검색된 결과를 적절하게 전달하는 데 사용됩니다. RAG 기술에서는 대량의 문서나 정보에서 필요한 정보를 검색하기 위해 검색 엔진을 사용하며, 이를 통해 생성 모델이 예측하기 어려운 지식이나 정보를 보다 쉽게 활용할 수 있습니다.

검색 증강 생성(Retrieval-Augmented Generation, RAG) 기술에는 검색엔진이 필요합니다. 이 기술에서는 먼저 검색 엔진을 사용하여 대량의 문서 또는 정보에서 적절한 문서나 정보를 검색합니다. 그런 다음, 검색된 문서나 정보를 생성 모델에 제공하여 보다 정확하고 일관된 응답을 생성합니다. 검색 엔진은 RAG 기술에서 중요한 역할을 담당하며, 검색 쿼리 처리 및 검색된 결과를 적절하게 전달하는 데 사용됩니다. RAG 기술에서는 대량의 문서나 정보에서 필요한 정보를 검색하기 위해 검색 엔진을 사용하며, 이를 통해 생성 모델이 예측하기 어려운 지식이나 정보를 보다 쉽게 활용할 수 있습니다.

검색 기반 모델과 생성 모델은 각각 다음과 같은 역할을 합니다.

  • 검색 기반 모델: 검색 기반 모델은 데이터베이스에서 원하는 정보를 검색하고, 검색된 정보를 임베딩하여 벡터 공간에 매핑합니다. 이 때 검색 기반 모델로 Faiss와 Pyserini를 사용할 수 있습니다. 예를 들어, 의료 정보에서 남녀 각각의 건강 문제에 대한 정보를 검색 기반 모델을 사용하여 찾아내고, 각각의 정보를 벡터로 매핑합니다.
  • 생성 모델: 생성 모델은 검색된 정보를 바탕으로 요약, 질문 응답 등 다양한 자연어 생성 태스크를 수행합니다. 이 때 생성 모델로 Hugging Face Transformers 라이브러리를 사용할 수 있습니다. 예를 들어, 검색 기반 모델에서 찾아낸 건강 정보를 바탕으로, 해당 건강 문제에 대한 자세한 설명을 제공하거나, 해당 문제와 관련된 질문에 대한 응답을 생성합니다.

따라서 검색 증강 생성(RAG) 기술은 검색 기반 모델로부터 검색된 정보를 생성 모델에 입력하여 보다 정확하고 일관된 응답을 생성할 수 있도록 합니다.

 

향후 RAG 기술의 현황과 발전 방향

현재까지는 Facebook AI Research의 RAG 기술이 가장 널리 알려진 RAG 기술입니다. 그러나 최근에는 다른 기업 및 연구자들도 RAG와 관련된 연구 및 개발을 수행하고 있습니다. 예를 들어, OpenAI에서는 GPT-3와 함께 RAG 모델을 사용하여 일부 벤치마크 작업에서 좋은 성능을 보였습니다. 또한, 구글의 연구자들은 T5와 함께 RAG 모델을 개발하고 있으며, Microsoft에서도 RAG와 관련된 연구를 수행하고 있습니다. 이러한 기업 및 연구자들은 각자의 방식으로 RAG 모델을 구현하고 활용하여, 향후 RAG 기술의 발전을 이끌어낼 것으로 기대됩니다.

RAG 기술의 발전 방향은 크게 두 가지로 볼 수 있습니다.

첫째, 검색 기반 모델과 생성 모델 간의 상호작용을 보다 개선하여 더욱 정확하고 효과적인 응답을 생성하는 것입니다. 예를 들어, 더 나은 검색 모델을 개발하거나 생성 모델의 학습 방식을 개선하여 더 나은 응답을 생성할 수 있도록 하는 등의 연구가 진행될 것입니다.

둘째, RAG 기술을 다양한 분야에 활용하는 방법을 연구하는 것입니다. 예를 들어, 의료 분야에서는 환자 진료 내역과 의학 논문 등을 검색 기반 모델에 활용하여 질병 진단과 치료 방법 등에 대한 전문적인 정보를 제공하는데 활용될 수 있습니다. 또한 법률 분야에서는 판례나 법률 관련 논문 등을 검색 기반 모델에 활용하여 전문가 수준의 법률 자문을 제공하는데 활용될 수 있습니다.

앞으로 RAG 기술은 보다 정확하고 효과적인 인공지능 서비스를 제공하는데 큰 역할을 할 것으로 기대됩니다.