38P by xguru 3일전 | ★ favorite | 댓글 5개
  • 몇 시간 동안 나만의 AI 이미지 모델을 훈련시켜, 직접 찍은 것 같은 사진을 만들어보려는 프로젝트를 시도해봤음
    • 예: "슈퍼맨" 분장을 한 내 사진을 생성
  • 시도한 이유: 재미있을 것 같았고, 아이들이 함께 놀기에 좋으며, 커스텀 모델/심화 AI 부분을 좀 더 배울 수 있음
  • 12~18개월 전에는 이 작업이 상당히 복잡했으나, 이제는 매우 간단해졌음
  • 2시간 이내에 모델을 만들어 원하는 이미지를 얻었으며, 핵심은 올바른 도구를 빠르게 파악하는 것이었음

모델/훈련 패턴 선택

  • 필요한 요소
    • 기반 모델(base model)
    • 훈련/파인튜닝 기법
    • 훈련 데이터셋(자신의 사진 몇 장 등)
  • 많은 AI들이 Stable Diffusion을 권장하지만, Pieter Levels가 사용한 Flux 모델이 더 나은 성능을 낸다고 하여 Flux를 선택
    • 완전히 최신 SOTA 모델은 아니지만 충분히 좋음
  • 훈련 기법으로는 LoRA(Low-Rank Adaptation)를 사용
    • 모델 전체를 재학습하지 않고, 특정 "매직 단어"에 연결된 부분만 훈련함
    • 예: "czue"라는 흔치 않은 단어를 모델에 학습시켜, 프롬프트에서 그 단어를 사용하면 해당 데이터셋의 특징을 반영함

훈련 세트 생성

  • 학습 대상의 사진 여러 장(10~15장 정도)을 준비해야 함
    • 표정, 배경, 조명, 각도 등이 다양할수록 좋음
    • 한 사진에 한 사람만 있는 것이 바람직함
  • 훈련 시에는 텍스트 설명이 필요하며, 여기에 매직 단어가 포함되어야 함
    • 예: "a photo of czue on the beach, wearing a blue shirt"
  • 하지만 최근 도구들은 자동으로 이미지 캡션을 생성해주므로, 직접 설명을 입력하지 않아도 됨

모델 훈련

  • 처음에는 로컬에서 훈련할 생각이었지만, GPU와 RAM이 부족해 어려움
  • GPU 클라우드 서버에서 직접 코드를 돌릴 수도 있으나, 결국 Replicate를 사용했음
    • GPU 임대 서비스이며, 이미 만들어진 레시피를 바로 사용할 수 있음
  • 이번 사례에서는 ostris/flux-dev-lora-trainer 레시피를 사용함
    • Replicate 계정 생성 후, 청구 정보를 설정해야 함
  • 주요 파라미터
    • input_images: 훈련 사진(zip)
    • trigger_word: 매직 단어 예) "czue"
    • hf_repo_id, hf_token: Hugging Face 레포지토리/토큰
    • autocaption_prefix: 자동 생성 캡션 앞에 붙일 문구 (예: "A photo of czue,")

Hugging Face에 모델 저장

  • Hugging Face는 모델을 저장·공유하는 플랫폼
  • Replicate도 학습된 모델을 어딘가에 저장해주지만, Hugging Face에 올리면 다른 툴과 연동하기 쉬움
  • 계정 및 모델 생성 후 hf_repo_id를 전달함
  • 학습이 끝나면 "lora.safetensors"라는 큰 파일(약 180MB)이 Hugging Face에 업로드됨

모델로 이미지 생성

  • 학습이 끝나면, 모델에 텍스트를 입력해 이미지를 만드는 추론(inference) 과정을 진행함
  • 로컬에서 직접 해볼 수도 있으나, 다시 Replicate를 활용했음
    • lucataco/flux-dev-lorahf_lora 필드만 설정하면 됨
      • public Hugging Face 레포지토리 ID 혹은 Replicate에 업로드된 학습 모델 링크
  • 예: "a photo of czue surfing"을 입력하면 다음처럼 서핑하는 본인 이미지를 얻음

프로그래밍 방식으로 모델 실행

  • 다양한 프롬프트를 시도하고 결과를 자동 저장하려면 API 호출 방식이 편리함
  • 아래 Python 스크립트를 예시로 작성했음 (전체 코드는 Github에 있음)
    # /// script  
    # requires-python = ">=3.12"  
    # dependencies = [  
    #     "replicate",  
    # ]  
    # ///  
    import argparse  
    import os  
    import re  
    import replicate  
    import uuid  
    
    DEFAULT_MODEL = "czue/me-v1"  
    DEFAULT_COUNT = 1  
    
    def get_input(prompt, model=DEFAULT_MODEL, count=DEFAULT_COUNT):  
        return {  
            "prompt": prompt,  
            "hf_lora": model,  
            "num_outputs": count  
        }  
    
    def main():  
        parser = argparse.ArgumentParser()  
        parser.add_argument("prompt", help="Prompt for the photo")  
        parser.add_argument("--model", default=DEFAULT_MODEL,  
                          help="Model to use (default: %(default)s)")  
        parser.add_argument("--count", default=DEFAULT_COUNT,  
                          help="Number of photos to generate (default: %(default)s)", type=int)  
        args = parser.parse_args()  
    
        input = get_input(args.prompt, args.model, args.count)  
        output = replicate.run(  
            "lucataco/flux-dev-lora:091495765fa5ef2725a175a57b276ec30dc9d39c22d30410f2ede68a3eab66b3",  
            input=input  
        )  
    
        output_dir = "output"  
        os.makedirs(output_dir, exist_ok=True)  
    
        prompt_slug = "-".join(args.prompt.split(" ")[-3:])  
        prompt_slug = re.sub(r'[^a-zA-Z0-9\-]', '', prompt_slug).lower()  
    
        for index, item in enumerate(output):  
            file_id = uuid.uuid4().hex[:5]  
            output_path = os.path.join(output_dir, f"{prompt_slug}-{file_id}.webp")  
            with open(output_path, "wb") as file:  
                file.write(item.read())  
                print(f"Saved photo {output_path}")  
    
    if __name__ == "__main__":  
        main()  
    
  • 예시 사용법
    uv run main.py "a photo of czue, a 40 year old man, writing a blog post" \  
     --model="czue/me-v1" \  
     --count=4  
    

결과

  • 모델 성능은 들쑥날쑥함
    • 사람 특징을 제법 비슷하게 잡기도 하지만, 가끔은 다른 인물을 만들어내기도 함
    • 특정 나이, 성별 등을 추가로 프롬프트에 명시하면 좀 더 정확해짐
  • 예를 들어, "a photo of czue, a 40 year old man, writing a blog post"는 비교적 일관된 이미지를 생성함
  • 반면 "a photo of czue writing a blog post"는 훨씬 결과가 제각각이었음
  • 다른 인물을 함께 넣으면 얼굴이 뒤섞이는 등의 문제가 발생함
    • 버락 오바마와 같이 있는 사진을 만들어보려고 했더니, 내 얼굴의 일정 부분이 오바마 쪽에 반영되고 반대도 마찬가지
  • 그래도 충분히 재밌고 유용해서 아이들과 함께 다양한 시도를 해볼 수 있었음

비용

  • 무료는 아니지만 그렇게 비싸지도 않음
  • 나와 아이들을 합쳐 모델 3개를 학습했는데, 각각 ~$2.50 정도였음
  • 이미지 생성은 한 장당 약 $0.03 정도라서 30장을 생성해도 $1 가량임
  • 전체 실험으로 $10 이하를 썼으며, 생각보다 비용 부담이 적어 만족스러웠음
  • AI 모델 훈련과 이미지 생성에 관심 있다면, 생각보다 쉽게 시도할 수 있으니 도전해볼 만함

재미삼아 따라해봤는데 정말 쉽군요.
(https://www.youtube.com/watch?v=sNpQ9ULDMoo)
이것저것 만들어보며 한참 웃었습니다...

결국 우리가 죽기 전에 나 처럼 훈련된 모델을 네트워크에 업로드하고 죽으려 하지 않을까요? 생존 본능처럼... 그게 '나'는 아니지만.

기술을 보니 소설 하나가 떠올라 소개합니다. 이유리 소설가가 낸 소설집 비숫방울 퐁에 실린 '크로노스'라는 단편인데요. 사람을, 그러니까 자신의 데이터를 저장하고 학습하는 ai 를 다룬 내용입니다. 저 댓글의 고양이처럼요. 치매에 걸린 엄마가 증상이 더 심해지기 전에 그걸 써요. 그리고 자식들은 갈등하죠. 위로받기도 하고 죄책감도 느끼고. 위 기술에, 또 저 고양이의 이야기에 관심 있는 분이라면, 한번 읽어보세요.

해커뉴스 댓글 하나가 눈길을 끄네요

  • 내가 사랑하는 죽은 고양이를 위해 이렇게 해봤음. 결과가 마음에 들긴 했는데, 어느 순간 갑자기 내가 하고 있는 일에 대해 소름이 돋았음
    • 큰 비즈니스가 될것 같음. 나는 아마도 수십만 통의 이메일, 문자, 채팅 등을 보냈을 텐데 사랑하는 사람의 커뮤니케이션 코퍼스를 학습시켜 그들이 세상을 떠난 후에도 '그들'과 채팅할 수 있도록 하는 것은 충분히 가능한 일
    • 아버지가 돌아가신 후 아버지의 목소리로 이 작업을 했고, LLM을 지원하는 비서와 대화할 수 있는 기능을 설정해 아버지의 목소리와 방식으로 응답하도록 했음. 매우 이상하게 대처하고 슬퍼하는 시기였고, 결국 내가 하는 일에 대해 정말 이상하다는 생각이 들었음
    • 이거 블랙 미러의 "Be Right Back" 에피소드" 와 비슷함

정보성 댓글도 하나

  • Flux의 경우, 텍스트 인코더에 훨씬 더 많은 기능이 있으며 더 의미 있고 포괄적인 문장으로 프롬프트할 수 있음
    • 따라서 Stable Diffusion에서 볼 수 있었던 기존의 쉼표로 구분된 간결한 문구는 줄여도 됨
  • 또한 훈련 이미지에도 동일한 작업을 수행해야 함. 모델이 '나'로 기억하지 않기를 바라는 모든 것(하고 있는 일, 입고 있는 옷, 동반한 사람, 액세서리 등)에 캡션을 달면 좋음

"충분히 발달한 과학기술은 마법과 구분할 수 없다 - Arthur C. Clarke"

불과 2년 전만 해도 SF 영화에서나 볼 법한 일인데, 우리가 정말 마법이 현실이 되는 순간을 실시간으로 목격하고 있네요 😳