전체 글 (339)

반응형

 

 

RAG 를 사용하지 않은 기본적인 API Call 방법

 

1. 기본 Openai API

from openai import OpenAI
import os
os.environ["OPENAI_API_KEY"] = "sk..."

client = OpenAI()
completion = client.chat.completions.create(
    model="gpt-4o",
    temperature=0.1,
    messages=[
        {"role":"system", "content":system_prompt},
        {"role":"user", "content":user_query}
    ],
    response_format={"type":"json_object"}
)
response = completion.choices[0].message.content
return(response)

2. Llama_index 활용

from llama_index.llms.openai import OpenAI
from llama_index.core.llms import ChatMessage
import os
os.environ["OPENAI_API_KEY"] = "sk..."

# OpenAI 모델 설정
llm = OpenAI(model="gpt-4o", temperature=0.1)

messages = [
    ChatMessage(role="system", content=system_prompt),
    ChatMessage(role="user", content="Hi"),
]

# Response 를 json 형식으로 받고 싶은 경우 
resp = llm.chat(messages, response_format={"type": "json_object"})
반응형
반응형

 

RAG Framework

RAG (Retrieval Augmented Generation) 은 LLM 이 정보를 검색하고 이를 활용하여 결과 생성할 수 있도록 하는 방법이다. 

 

RAG 의 4가지 컴포넌트

 

유명한 프레임워크로 LangChain 과 LlamaIndex 가 있다. 이 포스트에서는 두 프레임워크에 어떤 차이점이 있는지를 살펴보려고 한다. RAG 에는 4가지 컴포넌트가 있다. Loaders, Splitters, Indexing, Chains 이 그것이다. 각각 LangChain과 LlamaIndex 가 이를 어떻게 구현하고 있는지 살펴보자. 

 

1. Loaders

Loader 는 API, document, DB 와 같은 다양한 소스들을 로드하는 객체이다. LangChain, LlamaIndex 모두 흔하게 사용되는 소스들을 로드할 수 있는 built-in 함수들을 제공한다. 로더를 통해 파일 위치를 입력해주면 Directory 객체가 반환되며, 이를 다시 한 번 더 로드해주면 텍스트를 로드할 수 있다. 

 

LangChain: Text 파일 로드하기

from langchain.document_loaders import TextLoader
# Load a text document
loader = TextLoader("sample.txt")
documents = loader.load()
print(documents[0].page_content)

 

LlamaIndex: 특정 디렉토리안에 있는 텍스트 파일 로드하기 

from llama_index.core import SimpleDirectoryReader
# Load a text document from a directory
loader = SimpleDirectoryReader('path/to/docs')
documents = loader.load_data()
print(documents[0].text)

 

2. Splitters

Splitter 는 도큐먼트를 작은 Chunk 단위로 분해해서 GPT 나 BERT 의 token limit 을 넘기지 않도록 만들어주는 역할을 한다. 

 

LangChain

LangChain 의 `TextSplitter` 는 텍스트를 Character, Word, Sentence 중 어떤 단위로 분할할지를 선택할 수 있다. 아래 코드는 Character 단위로 텍스트를 분할하며, chunk size 는 1000이고, chunk 간에 200개의 중복 두는 방식으로 텍스트를 분할한다. 

 

from langchain.text_splitter import CharacterTextSplitter

# Define a character splitter
splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(documents)
print(chunks[0].page_content)

 

LlamaIndex 

LlamaIndex 에서는 아래와 같이 똑같은 작업을 수행할 수 있다. 

from llama_index import TokenTextSplitter

# Define a token splitter
splitter = TokenTextSplitter(chunk_size=1000)
chunks = splitter.split(documents)
print(chunks[0].text)

 

3. Indexing

Indexing 은 RAG system 에서 가장 핵심 부분이라고 할 수 있다. 유저 쿼리와 가장 관련있는 청크를 빠르고 효율적으로 검색할 수 있어야한다. VectorStoreIndex 라는 말이 자주 등장하는데, 이는 VectorStore 를 빠르게 검색해서 유저 쿼리와 연관된 청크를 반환해주는 색인 시스템을 의미한다고 이해하면 된다. 

 

LangChain

LangChain 의 VectorStoreIndex 는 Splitter 를 통해 만들어진 청크들을 통해 인덱스를 생성한다. 그리고 Similarity search 를 기반으로 동작하게 된다.

from langchain.vectorstores import FAISS
from langchain.embeddings.openai import OpenAIEmbeddings

# Create embeddings
embedding_model = OpenAIEmbeddings()

# Create FAISS index
index = FAISS.from_documents(chunks, embedding_model)

 

LlamaIndex

LlamaIndex 에서는  아래와 같이 Document 로부터 바로 VectorStoreIndex 를 생성할 수 있고, Splitter 를 바로 인자로 주어 생성할 수 있다. 

from llama_index.core import VectorStoreIndex
embed_model = OpenAIEmbedding(model="text-embedding-3-small")

index = VectorStoreIndex.from_documents(
    documents,
    embed_model=embed_model,
    transformations=[SentenceSplitter(chunk_size=16000)]
)

 

4. Chain

RAG 에서 Chain 이란 Retrieval 과 Generation 의 요소들이 결합된 일련의 작업들이라고 보면 된다. LangChain 과 LlamaIndex 모두 이러한 RAG 의 컴포넌트를 엮어서 하나의 체인으로 만드는 기능을 제공한다. 

 

LangChain

LangChain 에서는 아래와 같이 `RetrievalQA` 를 활용해서 LLM 과 Retrieval 가 결합된 체인을 만들 수 있다. 이를 통해 RAG workflow 를 좀 더 간결하게 표현할 수 있다. 

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI

# Combine FAISS index and OpenAI for RAG
llm = OpenAI()
qa_chain = RetrievalQA(llm=llm, retriever=index.as_retriever())
result = qa_chain.run("What is the content of the document?")
print(result)

 

LlamaIndex

LlamaIndex 에서는 VectorStoreIndex 에 as_query_engine 또는 as_chat_engine 을 이용해서 쉽게 Index 와 LLM 을 연결할 수 있다. 해당 Index 를 활용하는 LLM 을 만들라는 의미이다.  query engine 과 chat engine 의 차이점은 chat engine 은 컨텍스트를 기억한다. as_chat_engine 을 보면, memory 인자를 전달함으로써, 과거 기억을 얼마나 전달할지를 정할 수 있다. token_limit 이 작을수록 과거 컨텍스트를 모델에 더 적게 전달한다. 

# Chat Engine 생성하기
memory = ChatMemoryBuffer.from_defaults(token_limit=16000)
chat_engine = index.as_chat_engine(
    chat_mode="context",
    memory=memory,
    system_prompt=system_prompt
)
response = chat_engine.query("What is the content of the document?")
print(response)

 

LangChain 과 LlamaIndex 중 무엇을 선택해야할까?

1. Customization

LangChain 의 장점은 다양한 소스를 기반으로 RAG 를 구현해야할 때 더욱 유연하다는 것이다. 예를 들어, Multi-modal RAG 시스템을 구축한다고 해보자. PDF, API 문서, Web 문서 등을 각각 Load 해서 VectorStoreIndex 를 구축한다고 해보자. LangChain 에서는 `+` operation 을 통해 쉽게 로더를 결합할 수 있다.  

from langchain.chains import SequentialChain
from langchain.llms import OpenAI
from langchain.document_loaders import WebPageLoader, TextLoader
from langchain.vectorstores import FAISS

# Step 1: Load data from multiple sources
pdf_loader = TextLoader("docs/sample.pdf")
web_loader = WebPageLoader(url="https://example.com")
documents = pdf_loader.load() + web_loader.load()

# Step 2: Create embeddings and index
embedding_model = OpenAIEmbeddings()
index = FAISS.from_documents(documents, embedding_model)

# Step 3: Build a chain that retrieves and generates responses
llm = OpenAI()
qa_chain = RetrievalQA(llm=llm, retriever=index.as_retriever())

# Run the chain
response = qa_chain.run("What are the key points in the PDF and website?")
print(response)

 

LlamaIndex 의 장점은 더욱 직관적이며, 심플하게 RAG 를 구현할 수 있다는 것이다. 예를 들어 LlamaIndex 의 경우, 법 문서나 medical report 등을 기반으로 RAG 를 만들 때, 매우 빠르게 최소한의 코드로 구축해볼 수 있다. 

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# Load documents from a directory
loader = SimpleDirectoryReader('docs/')
documents = loader.load_data()

# Build a tree index and query
index = VectorStoreIndex(documents)
query_engine = index.as_query_engine()

# Query the system
response = query_engine.query("Summarize the legal document.")
print(response)

 

2. Ecosystem 통합

LangChain 은 여러 AI Ecosystem 들을 통합할 때 더욱 유용하다. LangChain 은 Multi-LLM, Multi-Retrieval 를 지원한다고 볼 수 있다. 

- Vector Database : FAISS, Pinecone, Chroma

- Language Model : OpenAI, GPT-4, Anthropic's Claude

- API : Hugging Face, Cohere

 

위와 같은 요소들을 자유롭게 결합할 수 있다는 것이 LangChain 의 장점이라고 볼 수 있다. 예를 들어, OpenAI 의 GPT-4 와 HuggingFace 의 BERT 를 태스크에 따라 자유롭게 스위치 할 수 있다.  

from langchain.chains import LLMChain
from langchain.chains import SimpleSequentialChain
from langchain.llms import OpenAI, HuggingFaceHub
from langchain_core.prompts import PromptTemplate

prompt_template = "Tell me a {adjective} joke"
prompt = PromptTemplate(
    input_variables=["adjective"], template=prompt_template
)

gpt_chain = LLMChain(llm=OpenAI(), prompt=prompt)
gpt_chain.invoke("scary")

bert_chain = LLMChain(llm=HuggingFaceHub(model="bert-large-uncased"), prompt=prompt)
bert_chain.invoke("scary")

 

반면 LlamaIndex 의 경우 LangChain 과 같은 유연성은 떨어지지만, 앞서 언급한 것처럼 빠르게 document 기반의 RAG 를 만들 때 유용하다고 할 수 있다. 

 

3. Multi-retrieval

LangChain 의 경우 Multi-retrieval 를 구현할 수 있다. 예를 들어, 유저 쿼리를 받아, 법과 관련된 쿼리라면, 법 문서에 적용된 key-based retrieval 를 호출하고, 과학 관련 쿼리라면, 과학 논문에 적용된 embedding-based retrieval 를 호출하는 multi-retrieval 를 구현해보자. 

from langchain.retrievers.multi_retriever import MultiRetriever
from langchain.retrievers import FAISSRetriever, KeywordRetriever

# Define keyword and embedding-based retrieval systems
keyword_retriever = KeywordRetriever(documents=legal_documents)
embedding_retriever = FAISSRetriever(index=scientific_index)

# Combine them in a MultiRetriever
retriever = MultiRetriever(retrievers={
    'legal': keyword_retriever,
    'science': embedding_retriever
})

# Query the retriever with a legal question
response = retriever.retrieve("What are the recent changes in contract law?")
print(response)

 

4. Community Support 측면

LangChain 은 위와 같은 advanced 된 기능들 때문에 최근 빠르게 관련 커뮤니티가 성장하고 있으며, Llama Index 와 비교하여 좀 더 성장된 ecosystem 을 갖고 있다. LlamaIndex 의 경우 비교적 최근에 등장했지만 마찬가지로 빠르게 관심을 얻고 있으며, 앞서 언급한 것처럼 좀 더 간단한 작업에 적합하다. 

 

결로: 결론적으로 어떤 상황에서 어떤 프레임워크를 선택해야할까?

LangChain 을 선택하면 좋은 상황
- 다양한 구성 요소들을 자유롭게 커스텀하고 싶을 때 (Retrieval, LLM, OpenAI, HuggingFace, FAISS, Chroma...)
- VectorStore 를 다양한 타입의 소스로 구성하고 싶을 때 (Text, PDF, API 등)
- 다양한 Retrieval 전략을 사용하고 싶을 때 (Keyworkd search, post-retrieval ranking 등)

LlamaIndex 를 선택하면 좋은 상황 
- 간단한 RAG System 을 구축하고 싶을 때 (예를 들어, document 를 요약해주는 AI 를 구현)
- 특별한 커스텀이 필요없을 때 
- 구조적인 문서 (법 관련 문서 등) 을 기반으로 RAG 를 구축할 때 잘 작동함 

 

 

 

 

 

출처

- https://medium.com/@tam.tamanna18/langchain-vs-llamaindex-a-comprehensive-comparison-for-retrieval-augmented-generation-rag-0adc119363fe

반응형
반응형

 

 

기본적으로 가상환경을 새롭게 생성하고 거기에 설치하는 것이 좋다.

 

가상환경 생성

virtualenv openai
python3 -m ipykernel install --user --name=openai

# 가상환경 activate
source ./activate

 

pysqlite3 설치

pip install pysqlite3-binary

# 내장된 pysqlite3 대신 pysqlite3 을 사용하도록 설정한다. 
import sys
import pysqlite3
sys.modules["sqlite3"] = pysqlite3

import sqlite3
print(sqlite3.sqlite_version)
>> 3.46.1

 

3.35 이상 버전에서 chromadb 를 사용할 수 있다.

import chromadb

 

 

반응형
반응형

 

기본적인 RAG 를 좀 더 개선해보기 (w/ langchain)

 

기본 RAG 의 개념 : 기본 RAG (Retrieval Augmented Generation) 는 query 를 입력으로 받아 vector store 로부터 relevant 한 chunk 를 검색해서 가져온 후, 이를 prompt 에 추가해서 최종적인 답변을 출력한다.

 

사용된 개념

1. Relevance Check : vector store 에서 retrive 해온 chunk 가 relevant 한지 LLM 을 활용해서 검증한다.

2. Hallucination Check  : retrieved chunk 를 참고해서 생성한 최종 답변이 hallucination 인지 여부를 LLM 을 활용해서 검증한다. 

 

RAG 를 사용하기 위한 프레임워크로 langchain 을 사용한다. (비슷한 역할을 하는 Llamaindex 라는 프레임워크도 있다.)

 

목적: 3가지 웹문서를 vector store 로 저장하고, 이에 기반해 사용자 질문에 답변하는 RAG 를 만든다.

 

1. 필요한 라이브러리 설치

%pip install langchain langchain-openai langchain-openai langchain_chroma langchain-text-splitters langchain_community

 

2. LLM 객체 생성

import getpass
import os

os.environ["OPENAI_API_KEY"] = "API_KEY 를 입력하세요."
from langchain_openai import ChatOpenAI

# openai의 gpt-4o-mini 모델 사용
llm = ChatOpenAI(model="gpt-4o-mini")

 

3. 문서를 로드한다.

- langchain 의 WebBaseLoader를 활용하며, beautiful soup 라이브러리를 활용해 html 태그 등을 parsing 해 본문 내용만 가져온다고 이해하면된다. 

import bs4
from langchain import hub
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

urls = [
    "https://lilianweng.github.io/posts/2023-06-23-agent/",
    "https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
    "https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]

# Load, chunk and index the contents of the blog.
loader = WebBaseLoader(
    web_paths=urls,
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
docs = loader.load()

 

4. 로드한 문서를 Split 해서 Vector store 로 저장한다.

- chunk size 는 1000  토큰이며, 200의 오버랩을 허용한다. 각 청크간에 약간의 중복을 준다는 의미이다. 

- chunk 를 vector 로 저장할 때, embedding 이 필요한데, openai 의 text-embedding-3-small 모델을 사용했다.

- retrieval 의 세팅으로 similarity 를 기준으로 6개의 chunk 를 가져오는 retrieval 객체를 정의한다. 

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings(model="text-embedding-3-small"))
# Retrieve and generate using the relevant snippets of the blog.
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={'k':6})

 

5. user query 를 입력으로 받아 관련도가 높은 chunk 를 retrieve 해온다.

- 가져온 chunk 6개를 1번 chunk : ... 2번 chunk : ... 이런 형식으로 텍스트 형식으로 저장한다. 

from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain import hub
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

prompt = hub.pull("rlm/rag-prompt")

user_query = "What is agent memory?"
retrieved_docs = retriever.invoke("what is agent memory?")
retrieved_docs_text = ""
for i in range(0,6) :
    doc_content = retrieved_docs[i].page_content
    retrieved_docs_text += f'{i+1}번 chunk: {doc_content}\n'

 

6. 목적에 맞는 최종 prompt 를 작성한다.

- relevance check 와 hallucination check 를 하여 기본 RAG 를 좀 더 개선하는 것이 목적이므로, 이에 대한 로직을 추가한 프롬프트를 작성한다. 전체적인 답변 생성 로직과 함께 필요한 정보 - 유저 query 와 retrieved chunk 를 제공해주어야한다. 

query = {"question": {f"""
     너는 유저로부터 query 를 입력 받아서 최종적인 답변을 해주는 시스템이야.

     그런데, retrieval 된 결과를 본 후에, relevance 가 있는 retrieved chunk 만을 참고해서 최종적인 답변을 해줄거야.
     아래 step 대로 수행한 후에 최종적인 답변을 출력해.

     Step1) retrieved chunk 를 보고 관련이 있으면 각각의 청크 번호별로 relevance: yes 관련이 없으면 relevance: no 의 형식으로 json 방식으로 출력해.\n
     Step2) 만약 모든 chunk 가 relevance 가 no 인 경우에, relevant chunk 를 찾을 수 없었습니다. 라는 메시지와 함께 프로그램을 종료해.
     Step3) 만약 하나 이상의 chunk 가 relevance 가 yes 인 경우에, 그 chunk들만 참고해서 답변을 생성해.
     Step4) 최종 답변에 hallucination 이 있었는지를 평가하고 만약, 있었다면 hallucination : yes 라고 출력해. 없다면 hallucination : no 라고 출력해.
     Step5) 만약 hallucination : no 인 경우에, Step3로 돌아가서 답변을 다시 생성해. 이 과정은 딱 1번만 실행해 무한루프가 돌지 않도록.
     Step6) 지금까지의 정보를 종합해서 최종적인 답변을 생성해

     답변은 각 스텝별로 상세하게 출력해

     아래는 user query 와 retrieved chunk 에 대한 정보야.

     query : {user_query}\n
     retrieved chunks: {retrieved_docs_text}"""}}

 

7. Prompt Template 을 기반으로 RAG Chain 을 호출해 최종 답변을 json 형식으로 출력한다.

- prompt template 은 프롬프트를 구조화된 형식으로 작성할 수 있게 도와주는 클래스이다. 

- rag chain 은 query-> prompt -> llm -> output 파이프라인을 하나의 함수로 실행할 수 있도록 해준다. 

parser = JsonOutputParser()

prompt = PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

rag_chain = (
    prompt
    | llm
    | StrOutputParser()
)

rag_chain.invoke({"query": query})

 

최종적으로 생성된 답변

json\n{\n  "step1": {\n    "1": "relevance: yes",\n    "2": "relevance: yes",\n    "3": "relevance: yes",\n    "4": "relevance: yes",\n    "5": "relevance: no",\n    "6": "relevance: no"\n  },\n  "step2": "All retrieved chunks have relevance.",\n  "step3": "The relevant chunks provide information about agent memory, which consists of both long-term and short-term memory. Agent memory is a long-term memory module that records agents\' experiences in natural language. Observations can trigger new statements, and the retrieval model assists in informing the agent’s behavior based on relevance, recency, and importance. Additionally, the reflection mechanism synthesizes these memories into higher-level inferences over time.",\n  "step4": "hallucination: no",\n  "step5": "Since hallucination is no, I will generate the answer again.",\n  "step6": {\n    "final_answer": "Agent memory refers to a long-term memory module that captures a comprehensive list of an agent\'s experiences in natural language, which can be used to inform the agent\'s behavior. It includes short-term aspects, such as in-context learning, as well as long-term memory that retains information over extended periods. The retrieval model helps prioritize memories based on relevance, recency, and importance, while the reflection mechanism synthesizes past events into higher-level insights that guide future behavior."\n  }\n}\n

반응형
반응형

 

아래 코드는 일별로 유저수와 그 유저의 피쳐 a,b,c 에 대해 각각 평균과 중위수를 구하는 코드이다. 이 코드는 상당히 시간이 오래 걸린다.

select 
date_id,
count(distinct user_id) as user_cnt,
avg(a) as avg_a,
avg(b) as avg_b,
avg(c) as avg_c,
percentile_approx(a, 0.5) as med_a,
percentile_approx(b, 0.5) as med_b,
percentile_approx(c, 0.5) as med_b
from table
group by date_id

 

그런데, user_cnt 와 평균을 구하는 코드를 제외한 아래 쿼리는 빠르게 실행된다. 왜 그럴까?

select 
date_id,
percentile_approx(a, 0.5) as med_a,
percentile_approx(b, 0.5) as med_b,
percentile_approx(c, 0.5) as med_b
from table
group by date_id

 

챗 GPT 를 활용해 답변을 생성해보니 아래와 같다고 한다.

 

유저수를 count하는 연산과 평균을 구하는 연산이 맵리듀스 분산 처리시(Big Data 환경을 가정) 각 노드에서 개별적으로 이루어져서 빠른 처리가 가능하지만, 중앙값을 구하는 percentile_approx 연산은 전체 데이터를 한 노드에서 모아서 처리해야 하기 때문에 상대적으로 시간이 많이 소요됩니다.

하지만, 여러 컬럼의 중앙값을 동시에 구하는 쿼리에서는 중앙값을 구하는 작업이 모든 컬럼에 대해 동시에 이루어지기 때문에 시간이 대폭 줄어듭니다.

즉, count와 avg는 분산처리가 가능하여 각각의 연산이 독립적이지만 percentile_approx는 분산처리가 어려워서 전체 데이터에 대한 연산이 필요하여 시간이 오래 걸린 것입니다. 두번째 쿼리는 percentile_approx만 있기 때문에 동시에 처리될 수 있어 속도가 빠른 것입니다.

확인하기 위해, 각각의 연산을 따로 실행하는 쿼리를 만들어 시간을 체크해보면 더 정확한 비교가 가능할 것입니다.

 

결론

- hive sql 에서 percentile_approx 는 분산처리가 어렵고 전체 데이터에 대한 연산이 필요하기 때문에 시간이 오래 걸린다. 

- hive sql 에서 percentile_approx 를 활용해 중위수를 구할 때에는 다른 집계는 하지 말고 중위수만 구하자. 그럼 조금 더 빠르다. 

반응형

'Tools > SQL' 카테고리의 다른 글

여러 테이블을 Full outer join 하기  (0) 2022.08.25
반응형

R studio server 에서 knitr 에러 발생시 해결 방법

아래와 같은 오류 또는 test_env 를 찾을 수 없습니다. 등, Rmd 파일을 knitr 을 통해 변환시킬 때 에러가 발생했다. 

 

에러 메시지 예시

  |................................                                 |  50%
  ordinary text without R code

  |.................................................................| 100%


processing file: Preview-b0c112a265.Rmd
label: unnamed-chunk-1

Quitting from lines 16-26 (Preview-b0c112a265.Rmd) 
Error in file(file, "rt") : cannot open the connection
Calls: <Anonymous> ... withVisible -> eval -> eval -> read.csv -> read.table -> file
Execution halted

 

해결: [Tool] - [Global Options] - [R Markdown] - Evaluate Chunks in Directory 를 current 로 변경

 

위와 같이 세팅을 변경하자 문제가 해결되었다. 위 문제로 인해 마크다운 문서를 작성할 때 Rstudio server 가 아니라 로컬에서 작업을 항상 했었는데 불편함이 존재했었다. 위 방법으로 깔끔하게 해결되었고, 앞으로 마크다운 문서 작성도 R studio server 에서 하면 될듯하다.

 

참고 : https://stackoverflow.com/questions/26994958/error-cannot-open-the-connection-in-executing-knit-html-in-rstudio

반응형
반응형

R - Distill + github pages 로 간단한 블로그 만들기

 

R markdown 형식을 html 로 변환해 호스팅하는 패키지로 blogdown 이 유명했는데 최근 많은 유저들이 distill 로 많이 갈아타고 있는듯하다. blogdown 의 경우 hugo 를 기본으로 블로그 템플릿이 만들어지는데 hugo 보다 distill 의 기본 포맷이 조금 더 학술적인 느낌이기 때문에 개인적으로 distill 이 R 관련 포스팅에 좀 더 적합한 느낌으로 보인다. 

 

Distill Manual : https://rstudio.github.io/distill/blog.html

The Distillery: Distill 매뉴얼은 조금 드라이하게 작성 되어 있는듯하다. distill 사용을 좀 더 잘하기 위한 tip 등을 공유하는 사이트로 The Distillery 가 있다. (https://distillery.rbind.io/tips_and_tricks) 여기에서는 Distill 을 사용할 때의 tips and tricksshowcase 를 공유한다. showcase 를 보면 다른 유저들이 distill 을 이용해서 블로그를 어떻게 운영하고 있는지를 볼 수 있어 개인 블로그를 만드는데 참고가 될 것이다. 

 

간단하게 Distill 과 Github pages 를 통해 10분 이내로 블로그를 만들어보자.

물론 github 계정, git 설치, R 설치는 기본적으로 필요하다.

 

Distill 설치

install.packages('distill')

 

Distill Blog Project 생성

 

위와 같이 블로그를 생성하면 홈폴더 바로 아래에 있는 하위 폴더에 "_site" 라는 폴더가 있는데 여기에 웹사이트 최초 시작지점인 index.html 파일이 존재한다. 이렇게 github pages 로 push 하면 [githubID].github.io/_site 가 블로그의 주소가 된다. 만약 [githubId].github.io 를 주소로 블로그를 호스팅하고 싶다면, _site 에 위치한 파일들을 모두 home 폴더로 옮겨준다. 그리고 홈폴더 바로 아래에 있는 _site.yaml 파일의 output_dir 을 "." 으로 설정한다. 

 

_site.yaml 파일은 다음과 같이 될 것이다. 

name: "[id].github.io"
title: "My Blog"
description: |
  My Blog
output_dir: "."
navbar:
  right:
    - text: "Home"
      href: index.html
    - text: "About"
      href: about.html
output: distill::distill_article
collections:
  posts:
    categories: true
    categories_metadata: true
    authors_metadata: true

 

본인의 github 에 [id].github.io 라는 이름의 레포지토리를  만들고 여기에 만든 코드를 push 를 해주면 블로그가 잘 생성된 것을 볼 수 있다. github pages 로 푸시하는 과정은 아래와 같다.  

cd [home] # 생성한 distill 프로젝트의 홈폴더로 이동한다. 
git init
git add --all
git commit -m 'first commit'
git remote add origin https://github.com/[id]/[id].github.io
git push origin master

 

아래와 같은 형태의 기본 Distill 템플릿의 블로그가 생성되었다.

 

 

* 또는 output_dir 을 docs 로 설정한 후, github pages 설정에서 source 폴더를 docs 폴더로 설정하는 것을 추천한다.

 

 

포스트 조금 수정해보기

 

1) 포스트에 카테고리 붙이기

기본적인 블로그에는 오른쪽의 CATEGORIES 메뉴가 없을 것이다. 카테고리를 만드는 방법은 단순히 포스트에 다가 category tag 를 붙이면된다. 카테고리를 직접적으로 지정할 필요없이 포스트에 붙어 있는 태그를 통해 전체 카테고리가 재구성된다. 

---
title: "Welcome to My Blog"
categories:
- Category1
- Category2
- Category3
description: |
  Welcome to our new blog, My Blog. We hope you enjoy 
  reading what we have to say!
author:
  - name: Nora Jones 
    url: https://example.com/norajones
    affiliation: Spacely Sprockets
    affiliation_url: https://example.com/spacelysprokets
date: 08-23-2024
output:
  distill::distill_article:
    self_contained: false
---

 

2) rmd 를 html 형식으로 변환하기

- 카테고리를 붙이는 등 rmd 파일을 수정했다면 이를 html 로 변환해줄 필요가 있다. 

- 기본적으로 rstudio 에서 build - Install and Restart 를 누르면 빌드된다. 

- 다만 build 를 통해서는 각 포스트가 html 로 변환되지는 않는다. 포스트를 변환하기 위해서는 각 rmd 파일에 대해 knitr 을 실행해서 rmd 파일을 html 파일로 변환해주면 된다. 

Knit 를 통해 rmd 파일을 html 파일로 변환함으로써 github pages 를 통해 호스팅할 수 있다. 

반응형
반응형

R - ggplot 의 유용한 add-on 패키지 ggrepel

 

R 에서 ggplot 을 통해 그래프를 그릴 때 불편했던 점이 텍스트 레이블을 차트에 표시하기 어려웠던 점이다. 엑셀로 그래프를 그리는 경우 레이블 옵션을 설정한 뒤 이를 마우스 드래그를 통해서 조정할 수 있으나, ggplot 에서는 hjust, vjust 를 통해 한땀한땀 레이블을 조정하곤했다. 

 

혹시나 레이블의 위치를 자동으로 바꿔주는 패키지가 있나 살펴보다가 ggrepel 이라는 패키지가 있다는 것을 알았다. 사용법은 매우 쉽다. 만드려고한 ggplot 에다가 +  geom_text_repel() 만 해주면 된다. 

 

 

 

텍스트간의 겹치는 것을 조정해주기도 하지만, line 이나 point 와 같은 그래프의 요소와 텍스트가 겹치는 것을 알아서 조정해주기도 한다. 아래의 line chart 를 보면 point 와 겹치지 않게 text 가 자동으로 위치가 조정된 것을 볼 수 있다. 

ggplot(aes(x=x, y=y, group=g, label=round(t,2)), data=data) +
  geom_line(aes(color=g), size=1) +
  geom_point(aes(color=g)) + 
  theme_bw(base_size=10, base_family = "Kakao Regular") +
  theme(strip.background =element_rect(fill="#DEEBFF"),
        strip.text.x = element_text(size = 10),
        legend.position="right", 
        plot.title = element_text(hjust = 0.5, size=12), 
        axis.text.x = element_text(size=10),
        legend.text=element_text(size=10),
        legend.title=element_text(size=15),
        panel.grid.minor = element_line(size = 1), 
        panel.grid.major = element_line(size = 1),
        plot.background = element_rect(colour = "#cdcdcd",size = 0.5)) +
  ggtitle("Plot Title") +
  labs(color = 'STRATA') +
  ylab("Y LAB") + xlab("X LAB")+
  geom_text_repel()

 

반응형
반응형

 

 

 

quantile_output 이라는 폴더 아래에 6월 1일~6월 30일 사이의 30개의 csv 파일이 존재한다. 각 파일들은 같은 형식이며, 각기 다른 날짜에 집계됐다는 점만 다르다. 이 경우에 1) Sys.glob 을 이용해 해당 파일들의 경로를 벡터 형태로 만든 후에 2) lapply 를 통해 각 파일들을 읽어 list of dataframes 를 만들고, 3) 마지막으로 bind_rows 를 통해 dataframe 으로 만들어주면 된다.  

result_files <- Sys.glob("quantile_output/quantile_2024-06*")
result_list <- lapply(result_files, function(x) read_csv(x))
merged <- dplyr::bind_rows(result_list)

 

반응형
반응형

 

벤 다이어그램에서 구할 수 있는 다양한 유사도 지표들

벤다이어그램에서 A, B 그룹의 유사도를 구해보자

 

분석을 하다보면 두 집합이 얼마나 교차하는가? 를 이야기하고 싶을 때가 있다. A 그룹 유저는 B 그룹 유저와 N% 겹치고, B 그룹 유저는 A 그룹 유저와 M% 겹칩니다! 라고 말할 수 있다. 하지만 대량으로 이러한 교차성에 대한 값을 구해서 비교해 본다고 하자. 좀 더 심플하고 요약된 지표가 있다면 비교가 용이할 것이다. 교차성에 대한 정보를 0~1 사이의 값으로 나타내는 지표에는 어떤 것들이 있을까? 그리고 그 지표들 간에는 어떤 차이가 있을까? 

 

우선, 벤다이어그램으로 표현한 데이터는 아래와 같이 binary vector 로도 표현할 수 있다. 예를 들어, 어떤 유저들이 A 서비스를 사용하는지 B 서비스를 사용하는지 여부를 binary vector 로 아래와 같이 나타낼 수 있다. 

Sample A서비스사용 B서비스사용
1 1 1
2 1 0
3 0 1
4 1 0
5 1 0

 

유사도란 A, B 두 벡터가 얼마나 유사한지를 보는 것으로 이해할 수 있다. 또는, 벤다이어그램에서 겹치는 부분이 얼마나 되는지를 의미하는 지표라고 해석할 수 있다. 

 

1. Jaccard Index

 

Jaccard index 는 가장 기본적으로 생각할 수 있는 유사도 지표이다. 이것은 벤다이어그램에서 교집합 크기를 합집합 크기로 나눈 것으로 정의된다. 벤다이어그램에서 교차되는 부분의 면적을 전체 원의 면적으로 나눈 것이다. 

 

$$ J(A,B) = \frac{{|A \cap B|}}{{|A \cup B|}} $$

$$ J(A,B) = 100 / 1300 = 0.08 $$

 

Jaccard index 전통적으로 두 문서의 유사도를 비교할 때 쓰이기도 한다. A 문서와 B 문서의 유사도를 비교할 때, 두 문서에 등장하는 단어들을 하나의 샘플로해서 A,B 문서에 속했는지 여부를 이진 벡터로 만들 수 있을 것이다. 최종적으로 A문서와 B문서의 이진벡터를 만들 수 있고, 이를 통해 Jaccard index 를 구할 수 있다. 

 

2. Dice coefficient

 

Dice coefficient는 두 집합의 교집합 크기의 두 배를 두 집합 크기의 합으로 나눈 값이다. 

 

$$ D(A,B) = \frac{{2|A \cap B|}}{{|A| + |B|}} $$

$$ D(A,B) = \frac{100*2}{1000+400} = 0.14$$

 

3. Kulczynski similarity index

 

A 의 크기가 1만, B의 크기가 10, 그리고 A교집합B가 9라고 하자. B의 입장에서는 90%가 A와의 교집합인데 Jaccard index 나 Dice coefficient 는 낮게 계산된다. Kulczynski similarity 는 A 와 B 각각에서 교집합의 비율을 평균낸 값이기 때문에, B 입장에서 90%인 교집합의 비율이 동등하게 지표에 반영된다. Kulczynski similarity 는 두 집합의 크기 차이가 클 때, 이를 보정하는 지표로 볼 수 있다. 

 

$$ K(A,B) = 0.5 * \left(\frac{|A \cap B|}{|A|} + \frac{|A \cap B|}{|B|}\right) $$

$$ K(A,B) = 0.5*(100/1000 + 100/400) = 0.18 $$

 

즉, Kulczynski similarity  두 데이터 셋 간의 크기 차이가 결과에 미치는 영향을 감소시킨다. 반면, Jaccard index 나 Dice coefficient 의 경우, A,B의 크기의 차이가 결과에 영향을 미친다.

 

4. Cosine similarity (코사인 유사도)

 

Cosine similarity 는 두 벡터의 코사인 값을 통해 유사도를 구하는 개념이다. 코사인 유사도는 이진 벡터가 아닌 데이터에서도 광범위 하게 사용되는 measure 이다. 예를 들어, 자연어 처리에서 두 워드 임베딩 값의 유사도를 볼 때 쓰이기도 한다. 벤다이어그램에서는 두 벡터가 이진 벡터이기 때문에 두 벡터의 내적은 (1,1) 인 행의 숫자, 즉 교집합의 크기를 의미한다. 또한, 각 벡터의 크기는 집합의 크기의 제곱근이다. 

중고등학교 시절 배우는 공식

 

$$ \cos(\theta) = \frac{A \cdot B}{|A| |B|} $$

$$ \cos(\theta) = \frac{A \cap B}{\sqrt{|A|} \sqrt{|B|}} $$

$$ \cos(\theta) = \frac{100}{\sqrt{1000} \sqrt{400}} = 0.16 $$ 

 

반응형