Textual Inversion 관련 논문은 이번 8월에 나온 따끈따끈한 모델이다

이 그림을 참고하면서, 현재 WEBUI에 구현된 Textual Inversion 방식대로 설명하겠다


0. 프롬프트 템플릿 파일 경로가 있는데, 기본으로 입력되어 있는 '\textual_inversion_templates\style_filewords.txt'을 열면

a painting of [filewords], art by [name]

a rendering of [filewords], art by [name]

같은게 적혀있는걸 알 수 있다.

이것은 프롬프트 템플릿으로, 여기 적혀 있는 줄 중 랜덤으로 하나가 선택되어 프롬프트로 사용된다. 여기서 [filewords]에는 훈련 이미지의 태그가 들어가게 되고, [name]에는 훈련시키고자 하는 임베딩 문자열이 들어가게 된다. 그래서 Textual Inversion에서는 [name]이 가장 중요하다! (반대로 하이퍼네트워크는 [name]이 필요없다)


1. 훈련을 시작하자! 위의 프롬프트 템플릿을 통해 예를 들어 'A photo of S*'가 입력하면 CLIP tokenizer에 의해 [508, 701, 73, 338, 265] 토큰 묶음으로 변경한다

여기서 S*는 우리가 훈련시키고 싶은 임베딩의 이름인데, 딱히 훈련 없이도 이미 'S*'라는 문자열은 [338, 265]라는 토큰과 대응되어 있다.

2. 이 토큰 묶음의 맨 왼쪽에 시작토큰(42604)을 넣고, 토큰 묶음의 길이가 77이 될때까지 문자열 끝토큰(42605)를 채워넣는다.

[42604, 508, 701, 73, 338, 265, 42605, 42605, ..., 42605]

3. 이 묶음을 CLIP 트랜스포머 모델을 통해서 77x768 크기의 고정된 임베딩으로 변환한다 


대충 이부분

이 임베딩 안에 바로 프롬프트를 이미지로 만들어주기 위한 정보가 들어가 있다

그리고 위 그림처럼 임베딩의 각 행은 하나의 토큰에 대응되어, 그 토큰에 대한 정보가 들어있는 것이다.


Textual Inversion은 기존 S*의 정보를 대체하는 새로운 '부분 임베딩'(위의 그림의 ? 부분)을 새로 삽입해서, 그 부분만 학습시키고자 하는 것이다


여기서 토큰별 벡터 수는 이 '부분 임베딩'의 크기를 설정하는 것이고 (따라서 75보다 클 수 없음)

초기화 텍스트는 이 '부분 임베딩'에 처음으로 채워넣을 텍스트의 임베딩 데이터를 채워넣음. 즉 시작점임

처음 초기화 텍스트가 *로 되어있어서 이렇게 넣으면 이름이랑 똑같아지는 건가? 라고 생각할 수 있는데 그딴건 없고 진짜 '*'문자열에 대응하는 [265] 토큰의 임베딩 정보 그냥 집어넣는거임. 훈련할 임베딩의 정보를 노이즈로 채워넣고 시작하는거. 근데 어짜피 훈련 진행하다보면 결과적으로는 어느 범위 안에 수렴하게 되어있어서 초기화 텍스트에 뭘 집어넣던간에 훈련에 있어서 유의미한 차이가 없음. 욕심 그득하게 채워넣어봤자 토큰별 벡터 수의 범위 안에 내용이 짤림.


4. 이 77x768 임베딩을 diffusion 모델에 넣어서 '원래 하던대로' noise로부터 step을 밟아나가면서 그림(우리가 아는 512x512가 아니라 64x64 latent space이지만)을 그리고, 그걸 훈련 이미지를 노이즈화한 것과 비교해서 loss를 측정함. 이것이 모델 훈련으로 치면 forward 과정.


5. 이 부분이 가장 중요함. forward 과정이 있으면 backward 과정도 있어야겠지? backward라는 것은 위에서 뽑은 loss 값을 통해 loss 값이 작아지게 하는 임베딩 수치를 역산하는 과정임. 

역산 과정을 통해 임베딩 수치가 전부 변해야 하는데... 근데 우리는 'S*'만 훈련하고 싶지, 다른 토큰들의 수치는 건드리고 싶지 않잖아? 그래서 WEBUI에서는 우리가 훈련시키고 싶은 S* 부분 임베딩만 optimizer에 넣어서 얘만 값이 변하도록 만들었음. 다른 토큰들에 대한 임베딩 정보도 역산을 통해서 변했겠지만, 그 변화를 무시하고 S*만 계속 변화시키도록 훈련시키는거임.


논문 예시는 'A photo of S*'를 썼는데 CLIP 모델(과 훈련된 모델)은 어느정도 문장 해석 능력이 있기 때문에 저 문구에서 제일 중요한게 S*라는걸 이미 알고 있음. 프롬프트를 저렇게 쓰면 S*에 해당하는 부분 임베딩이 그림에 가장 큰 영향을 미치겠지? S* 하나만 가지고 훈련하는 것과 비슷해서 훈련이 잘 됨.


근데 예를 들어서 내 훈련 데이터셋 중에 이런 그림이 있고 'light red background, hatsune miku, aqua hair, long hair, open mouth, teeth, simple background, twintails, bare shoulders, vest, crazy smile, collarbone, 1girl, solo, portrait, looking at viewer'라고 태그를 썼다고 가정하자.

그러면 프롬프트는 'light red background, hatsune miku, aqua hair, long hair, open mouth, teeth, simple background, twintails, bare shoulders, vest, crazy smile, collarbone, 1girl, solo, portrait, looking at viewer, art by S*'이 될 것이다. 


모델: 아하하... 역산해봤는데 그렇게 aqua hair 같지도 않고 hatsune miku 같지도 않아서 임베딩 벡터를 요리조리 바꿔봤어요. 어때요?

나: 조까, S*이나 신경쓰고 나머지 변화는 싹 다 버려.

모델: 힝ㅠ

이러니까 임베딩 훈련의 loss 값이 제대로 수렴할 수가 없음. 다른 태그의 정보를 그대로 유지하면서 그림이 훈련 이미지에 근접하게 한다? 천원으로 단팥빵 두개 사고 잔돈 남겨오라고 시키는거랑 같다. 과적합? 어림도 없다 암!


그리고 또 다른 문제가 있는데, 이렇게 임베딩과 함께 태그를 잔뜩 넣어서 훈련을 진행시키면, 그 태그의 성질을 가지고 있지 않은 정보만 임베딩에 들어가게 된다.

이건 확실히 예시를 통해 보여줄 수 있는데, 프롬프트 템플릿에 1girl, solo, portrait, looking at viewer, art by [name]을 넣고 훈련했더니

프롬프트에 [name]만 넣으니까 이딴게 나온다.

1girl, solo, portrait, looking at viewer가 아닌 정보가 임베딩에 학습되었음을 알 수 있다....


요약

1. 태그를 존나 많이 박으면 오히려 임베딩만의 학습효과가 떨어진다

2. 태그를 넣을거면 훈련이미지에서 내 관심 대상과 벗어나는 것들에 대해서만 적기

예를 들어 누끼따서 전부 흰색 배경이 있다면 태그에 white background, simple background를 적어놓아야 임베딩에 흰색 배경이 덜 들어감


그리고 토큰벡터수 크게 잡지마라. 1~3 벡터면 다 표현 가능하다 (캐릭터면 좀 더 많아야 할지도)

이것도 증명할 수 있음

위: 2벡터, 프롬프트 템플릿: [name]
중간: 4벡터, 프롬프트 템플릿: [filewords], art by [name], 태그 직접 고침

아래: 임베딩 없이

대충 위가 우리가 원하는 퀄리티라는건 알겠지?