🔍 django 추천시스템 가장 쉽게 구현하기

2024-05-13

django 에서 추천시스템을 구현하려면 어떻게 해야할까요?

정말 다양한 방법이 있겠지만, Keep it simple 이란 말이 있듯이 가장 간단하게 구현해볼 수 있는 방법을 알아봅시다.



컨텐츠 유사도 기반 추천시스템

컨텐츠가 서로 얼마나 유사한지를 비교해서 유사도가 높은 컨텐츠를 추천해주는 방식입니다.

컨텐츠끼리 얼마나 유사한지를 알려면 컨텐츠를 좌표로 변환해야 합니다.

컨텐츠A => [8, 15]
컨텐츠B => [12, 13]
컨텐츠C => [6, 5]

embedding



좌표로 표현된 컨텐츠 끼리는 서로의 거리를 계산할 수 있습니다.

위 예시는 2차원으로 표현했지만, 3, 4차원… 1만차원도 동일합니다.

컨텐츠A => [81.31231, 15.59872, ... 12.34123]
컨텐츠B => [12.12314, 13.67893, ... 83.31564]
컨텐츠C => [461.9872, 5.354166, ... 13.67243]

이러한 컨텐츠의 수학적 표현을 임베딩이라고 부릅니다.



임베딩

텍스트, 이미지, 비디오 모두 임베딩으로 변환될 수 있습니다.

유사도를 잘 비교하기 위해서는 문서가 적절한 AI 모델을 통해 임베딩되어야 합니다.

제가 추천하는 임베딩 모델은 OpenAI 의 최신 임베딩 모델인 text-embedding-3-small 입니다.



text-embedding-3-small 은 텍스트를 1,536 차원의 임베딩으로 만들어줍니다.
비용도 매우 저렴하고, 한글에 대한 임베딩 성능도 꽤 괜찮습니다. 개인적인 의견이지만, 지금 기준으로는 더 좋은 임베딩 모델이 없는 것 같습니다.

해당 모델을 사용하는데 드는 비용은 1백만 토큰에 0.02달러 입니다.
대략 책 5 ~ 10권을 통째로 임베딩 해도 27원 정도 드는 셈이네요.



openai embedding 사용방법

openai 임베딩 모델을 사용하기 위해서는 먼저 python 패키지를 설치해줘야 합니다.

pip install openai

그리고 실제 사용 방식은 아래와 같습니다.

from openai import OpenAI


client = OpenAI(api_key="sk-proj-blahblahblahblahblahblah")


def create_embedding(text):
    return client.embeddings.create(
        input=[text],
        model="text-embedding-3-small").data[0].embedding


embedding1 = create_embedding("django 는 강력한 python 웹프레임워크입니다.")
embedding2 = create_embedding("python 은 처음 배우기 좋은 프로그래밍 언어입니다.")
embedding3 = create_embedding("react 와 함께 프론트엔드 개발하기")

print(embedding1[:10])
print(embedding2[:10])
print(embedding3[:10])

실행 결과는 다음과 같습니다.

[-0.014779764227569103, 0.04431036859750748, 0.006599314045161009, ... ]
[-0.03325946256518364, 0.030886657536029816, -0.04379631578922272, ... ]
[-0.0325852669775486, -0.01150344405323267, -0.0028920010663568974, ... ]


embedding 간 유사도 계산

유사도 계산에는 다양한 방법이 사용되지만, 가장 널리 사용되는 코사인 유사도를 기반으로 계산해봅시다.

scipy 라는 라이브러리를 이용하면 쉽게 계산해볼 수 있습니다.

from scipy import spatial


# django 는 강력한 python 웹프레임워크입니다. vs python 은 처음 배우기 좋은 프로그래밍 언어입니다.
result1 = spatial.distance.cosine(embedding1, embedding2)

# django 는 강력한 python 웹프레임워크입니다. vs react 와 함께 프론트엔드 개발하기
result2 = spatial.distance.cosine(embedding1, embedding3)

print(result1)
print(result2)

실행결과 (숫자가 0에 가까울 수록 유사함)

0.5632394123335309
0.7933444694105125


벡터 데이터베이스

유사도 비교를 위해 모든 컨텐츠를 매번 새로 임베딩할 필요는 없습니다.

대신 컨텐츠를 미리 임베딩하여 데이터베이스에 저장해두고, 필요할 때 꺼내서 계산하면 됩니다.

이러한 저장을 전문적으로 하는 데이터베이스를 벡터 스토어, 혹은 벡터 데이터베이스라고 부릅니다.


Pinecone, Chroma, FAISS 등 여러가지 옵션이 존재하지만, django 에서 가장 간단한 방법은 역시 Postgres 일 것 같습니다.

벡터 데이터베이스를 별도로 운용하는 것은 시스템 복잡도를 필요 이상으로 늘릴 수 있습니다.

Postgres 는 메인 데이터베이스로서 동작하면서, 동시에 벡터 데이터베이스의 역할을 훌륭하게 수행할 수 있습니다.



pgvector

Postgres 가 벡터 데이터베이스의 역할을 하기 위해서는 pgvector 라는 Postgres 확장을 설치해야합니다.

아래 pgvector github 에서 본인의 환경에 맞는 방식으로 pgvector 를 설치해줍니다. https://github.com/pgvector/pgvector

window, mac, linux, docker 등의 환경을 지원합니다.

저는 mac 환경에서 brew 를 통해 설치하였습니다.

brew install pgvector

설치 이후에 아래와 같은 명령어를 psql 에서 입력해주면 pgvector 확장이 활성화됩니다.

CREATE EXTENSION vector;


django pgvector

Postgres 에 pgvector 를 추가했으면 이제 django 에서 활용할 차례입니다.

python 의 pgvector 라이브러리를 통해 django 의 모델에 VectorField 를 추가할 수 있습니다.

pgvector python

먼저 pgvector python 패키지를 설치합니다.

pip install pgvector

django ORM 을 그대로 활용해서 임베딩을 사용할 수 있다는 점이 정말 큰 장점으로 작용합니다.

사용 방법은 아래와 같습니다.


빈 마이그레이션 파일을 만들어주고,

python manage.py makemigrations items --empty

마이그레이션 파일 안에 벡터 확장을 사용하겠다는 코드를 아래와 같이 작성해줍니다.

from pgvector.django import VectorExtension

class Migration(migrations.Migration):
    operations = [
        VectorExtension()
    ]

그리고 임베딩을 저장할 필드를 모델에 추가해주기만 하면 준비는 끝납니다.
이제 임베딩을 Postgres 에 저장할 수 있습니다.

# models.py
from pgvector.django import VectorField

class Item(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()

    # openai의 text-embedding-3-small 모델에 맞게 1,536 차원으로 설정
    embedding = VectorField(dimensions=1536)

django pgvector 유사도 정렬

임베딩 이후에 유사도 기준 정렬을 하려면 아래와 같이 코드를 작성할 수 있습니다.

먼저 embedding 을 추가하는 코드를 작성합니다.

# models.py
from openai import OpenAI
from pgvector.django import VectorField


client = OpenAI(api_key="sk-proj-blahblahblahblahblahblah")


class Item(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    embedding = VectorField(dimensions=3)

    def create_embedding(self):
        return client.embeddings.create(
            input=[self.title, self.content],
            model="text-embedding-3-small").data[0].embedding

    def save(self, *arg, **kwargs):
        # 모델이 DB 에 저장될 때 마다 title 과 content 로 임베딩 생성
        self.embedding = self.create_embedding()
        return super().save(*arg, **kwargs)

item object 를 생성해줍니다.

Item.objects.create(
    title="갤럭시 S22 Ultra 128gb A급 팝니다",
    content="배터리 성능 77%이지만 배터리 교체하셔서 사용하시는게 좋을 듯 합니다",
)
Item.objects.create(
    title="갤럭시 Z플립3 블랙",
    content="투명케이스 포함. 생활기스 조금 있는 s급",
)
Item.objects.create(
    title="캐논 렌즈 EF 50mm 팝니다",
    content="렌즈 링에 녹 없으며 사용감 있지만 성능에는 문제 없습니다.",
)
Item.objects.create(
    title="소니 카메라 zv-1f 판매합니다",
    content="카메라 + 슈팅그립 + 여친렌즈. 충전기 포함입니다",
)

코사인 유사도를 사용하려면 같은 방법으로 import 하여 사용 가능합니다.

from pgvector.django import CosineDistance


def my_view(request, item_pk):
    my_item = Item.objects.get(id=item_pk)

    distance = CosineDistance('embedding', my_item.embedding)
    recommendations = Item.objects.exclude(id=item_pk).order_by(distance)[:1]

    context = {
        "recommendations": recommendations
    }
    return render(request, "item_detail.html", context)

이렇게 간단하게 django 에 추천시스템을 구현해볼 수 있습니다.




👈 목록으로 돌아가기
😁 읽어주셔서 감사합니다