<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hong7_.log</title>
        <link>https://velog.io/</link>
        <description>데이터와 파이썬을 좋아합니다 :) contact : chal405@naver.com </description>
        <lastBuildDate>Mon, 07 Jul 2025 07:40:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hong7_.log</title>
            <url>https://velog.velcdn.com/images/hong7_/profile/d43cbb39-e425-4d88-85ac-1ada4b55f3bf/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hong7_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hong7_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[☁️ AWS Personalize로 추천 시스템 구축: 행동 데이터 추가로 성능 2배 향상시키기(feat: Airflow)]]></title>
            <link>https://velog.io/@hong7_/AWS-Personalize%EB%A1%9C-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95-%ED%96%89%EB%8F%99-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B6%94%EA%B0%80%EB%A1%9C-%EC%84%B1%EB%8A%A5-2%EB%B0%B0-%ED%96%A5%EC%83%81%EC%8B%9C%ED%82%A4%EA%B8%B0feat-Airflow</link>
            <guid>https://velog.io/@hong7_/AWS-Personalize%EB%A1%9C-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95-%ED%96%89%EB%8F%99-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B6%94%EA%B0%80%EB%A1%9C-%EC%84%B1%EB%8A%A5-2%EB%B0%B0-%ED%96%A5%EC%83%81%EC%8B%9C%ED%82%A4%EA%B8%B0feat-Airflow</guid>
            <pubDate>Mon, 07 Jul 2025 07:40:07 GMT</pubDate>
            <description><![CDATA[<p>클라우드 서비스를 이용한 추천 시스템 구축과 성능 개선을 위한 데이터 파이프라인 구축 및 재학습 파이프라인 구축 과정을 공유하고자 합니다</p>
<p>🧑‍💻 상황: 문서도 없고, 데이터도 낯선 상태</p>
<p>빠르게 추천 시스템을 구축해야 하는 상황이었습니다. 하지만 팀에는 데이터 히스토리를 잘 아는 멤버도, 문서도 없는 상태였습니다. 다른 팀원들이 외국에서 원격 근무 중이라 실시간 협업도 쉽지 않았습니다.</p>
<p>우선 데이터베이스를 살펴보니 상품 정보와 구매 데이터는 있었지만, 사용자 정보와 행동 데이터가 부족해 학습에 쓸 feature가 충분하지 않다는 생각이 들었습니다.</p>
<p>💡 이런 환경에서 직접 모델을 만들기보다 기존 모델에 데이터를 넣어 학습시키는 방식이 현실적이라는 판단을 했습니다.</p>
<p>팀은 AWS를 메인 클라우드 서비스로 사용하고 있었고, 리서치 끝에 Bedrock, SageMaker, Personalize를 검토했습니다. 이 중 적은 feature로도 빠르게 개인화 추천을 제공할 수 있는 Personalize를 최종 선택했습니다.</p>
<h2 id="aws-personalize를-이용한-추천-시스템-구현-ver-1">AWS Personalize를 이용한 추천 시스템 구현 ver 1.</h2>
<hr>
<p>Amazon Personalize는 내부적으로 Amazon의 추천 시스템을 기반으로 하고 있으며, 사용자 데이터를 학습해 API 형태로 추천 결과를 제공합니다. 다만 내부 모델이 어떻게 동작하는지는 블랙박스입니다.</p>
<p>📌 장점</p>
<ul>
<li><p>최소한의 데이터 스키마만 만족하면 빠르게 학습 가능</p>
</li>
<li><p>API 연결로 서비스에 빠른 적용 가능</p>
</li>
</ul>
<p>🗄️ 데이터셋 구성 예시</p>
<ul>
<li>Interaction Dataset </li>
</ul>
<table>
<thead>
<tr>
<th><strong>user_id</strong></th>
<th><strong>item_id</strong></th>
<th><strong>event_type</strong></th>
<th><strong>timestamp</strong></th>
</tr>
</thead>
</table>
<ul>
<li>Item Dataset </li>
</ul>
<table>
<thead>
<tr>
<th><strong>item_id</strong></th>
<th><strong>price</strong></th>
<th><strong>category_L1</strong></th>
<th><strong>category_L2</strong></th>
<th><strong>is_available</strong></th>
</tr>
</thead>
</table>
<ul>
<li>User Dataset </li>
</ul>
<table>
<thead>
<tr>
<th><strong>user_id</strong></th>
<th><strong>created_at</strong></th>
</tr>
</thead>
</table>
<p>데이터베이스에서 데이터를 추출해 변환한 뒤 S3에 적재하고, Dataset → Solution(Model) → Campaign을 생성하면 API 호출로 추천을 받을 수 있습니다.</p>
<p>Dataset을 위해 Product DB에서 추출한 데이터를 변환하여 S3에 적재하는 과정이 필요하며, 적재 후에 Dataset 업데이트, 솔루션 업데이트, 캠패인 업데이트를 각각 트리거하면 됩니다. 각 과정이 끝났는지 폴링을 하며 센싱을 한 뒤에 뒤의 과정을 트리거하는 방식을 채택했습니다.  </p>
<p>이 때까지는 구매 데이터만 사용했기 때문에 구매 주기를 분석하여 배치 학습의 주기를 1일로 지정하였습니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/145324e4-6a33-4508-9436-70997f6520ed/image.png" alt=""></p>
<p>🔄 재학습 파이프라인 (Ver1)</p>
<ul>
<li><p>데이터 추출/변환 → S3 적재 → Dataset 업데이트 → Solution 업데이트 → Campaign 업데이트</p>
</li>
<li><p>각 과정 완료 여부는 폴링 센싱 후 다음 단계 트리거</p>
</li>
<li><p>Cron 스케줄러로 재학습 주기: 1일 (구매 데이터 기반)</p>
</li>
</ul>
<p>🛡️ Cross-account 문제 해결</p>
<p>메인 서버와 Personalize 인프라가 서로 다른 계정에 있어 Cross-account pass role is not allowed 오류가 발생.✅ 해결: IAM에 sts:AssumeRole 권한 추가 → assumeRole()로 임시 권한 발급 → 호출 성공</p>
<h2 id="사용자-행동-데이터를-추가한-ver-2">사용자 행동 데이터를 추가한 Ver 2.</h2>
<hr>
<h3 id="--🕵️♀️-사용자-행동-데이터-추적하기">- 🕵️‍♀️ 사용자 행동 데이터 추적하기</h3>
<p>구매 데이터를 이용해 학습했을 때, 구매 이력이 있는 유저에 한하여 추천이 가능하고 구매는 sparse한 데이터이기 때문에 구매 외에도 더 다양한 interaction data가 필요했습니다. </p>
<p>프론트 엔지니어분과 이야기를 나누었을 때 과거 GA를 사용했었지만 지금은 관리가 되고 있지 않은 것을 알 수 있었습니다. 과거 심어져 있던 GA 이벤트를 추적하며 해당 GA 이벤트가 exprot되고 있는 bigquery를 발견할 수 있었습니다. (문서화가 되있지 않은 상태로 관리자가 바뀌며 자연히 잊혀진 상태였습니다) 이 과정은 마치 <strong>먼지가 잔뜩 쌓인 창고를 발견하는 것</strong>과 같았습니다. </p>
<p>Bigquery를 이용해서 현재까지 수집이 문제 없이 되고 있는 데이터와 되지 않는 데이터를 분류하고, front 코드를 기반으로 해당 필드가 Database의 어떤 필드와 연결되는지, 어떤 의미를 갖는지 정리하여 데이터 Taxonomy를 만들었습니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/b4c4f03a-2f55-4a2a-85ed-78192cb63157/image.png" alt=""></p>
<h3 id="--airflow를-이용한-데이터-elt-etl-재학습-파이프라인-구축">- Airflow를 이용한 데이터 ELT, ETL, 재학습 파이프라인 구축</h3>
<p>airflow를 선택한 이유는 여러 가지 task들의 성공 실패 여부를 웹 UI를 통해 빠르게 확인 가능하기 때문에 혼자서도 관리하기 수월하기 때문입니다. Bigquery 뿐만 아니라, python Operator 또는 python 함수를 task로 만들어 사용할 수 있는 유연성도 존재하여 이후에 대시보드나 주요 리포트 생성으로도 확장 가능하기 때문에 채택하게 되었습니다. </p>
<p>Airflow는 인프라 엔지니어분과 논의하여 AWS MWAA를 이용해 배포하였고, 위와 마찬가지고 Worker에 대한 Cross-account 문제는 sts:AssumeRole 권한을 추가하고 assumeRole()을 통해 해결해주어야 합니다. </p>
<p>GA 데이터는 exprot를 통해서 bigquery에 적재가 되었고, Product DB의 경우 GCP의 Datastream을 통해 bigquery 테이블에 적재하였습니다.</p>
<h4 id="ga-데이터-병합">GA 데이터 병합</h4>
<p>GA데이터는 event_date에 따라 별개의 테이블로 적재가 됩니다. 이를 하나의 테이블로 합쳐서 관리하기 위해서 event_date를 partition으로 하는 하나의 raw table을 생성하였습니다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ccbce8f9-7c84-46b7-919c-8c60f5a03b0a/image.png" alt=""></p>
<p>GA 데이터가 적재되는 시간을 매번 다른데, 해당 파이프라인이 동작할 때 먼저 해당 날짜의 GA 데이터가 적재되었는지 먼저 체크하고 있다면 병합을 진행하도록 했습니다. 이는 나중에 <strong>fail이 되었다면 원천 데이터의 누락인 것을 인지</strong>하고 확인 후 수동으로 트리거하기 위함이었습니다. </p>
<h4 id="ga-데이터-정제">GA 데이터 정제</h4>
<p>병합된 raw 테이블에서 각 event를 기준으로 필요한 데이터를 정제하여 별개의 테이블로 관리했습니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/3bc9e4f6-d497-4772-8914-4a3a8179f31e/image.png" alt=""></p>
<p>데이터가 존재하는지 확인 -&gt; 테이블이 있는지 확인 후 없으면 생성 -&gt; 데이터 insert 과 정으로 진행되었습니다. {{ ds }} 값을 partition으로 지정하여 관리했습니다. 이를 통해 Product DB에는 없는 <strong>유저의 행동 데이터를 사용</strong>할 수 있게 되었습니다 </p>
<h4 id="personalize-데이터-etl">Personalize 데이터 ETL</h4>
<p>이제 준비된 GA 데이터와 Product DB의 데이터를 학습에 사용될 스키마 형태로 가공하여 S3에 적재하는 ETL 파이프라인을 구축할 수 있게 되었습니다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ee7e4049-12f9-4786-b963-0f3cfcf3cb01/image.png" alt=""></p>
<p>유저의 행동 데이터와 구매 데이터를 병합하여 Interaction 데이터로 만들고, 나머지 User, Item에 대한 추출과 변환도 진행해주었습니다. S3 적재와 Personalize Python 함수를 task로 만들어 진행하였습니다. </p>
<p>이 때 유저에게 지속적으로 새로운 Item set을 노출시켜주는 것이 중요하다고 판단하였기에, GA 데이터의 정제의 성공 실패 여부와 별개로 Personalize Dag는 수행되도록 하였습니다. Personalize는 같은 Dataset이여도 새로운 학습을 진행하면 추천 항목이 달라지기 때문입니다. </p>
<h3 id="행동-데이터-추가-이후-성능-지표-변화">행동 데이터 추가 이후 성능 지표 변화</h3>
<hr>
<p>Personalize에서는 Solution 별로 추천 성능 지표를 제공합니다 
두 모델 (첫 번째 = Ver1, 두 번째 = Ver2) 의 성능 지표를 비교했을 때 다음과 같은 향상이 있었습니다 </p>
<table>
<thead>
<tr>
<th>지표</th>
<th>첫 번째 모델</th>
<th>두 번째 모델</th>
<th>변화</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>NDCG @25</strong></td>
<td>0.1973</td>
<td>0.3380</td>
<td>🔼 +71%</td>
<td>관련 아이템이 추천 리스트 상위로 더 이동</td>
</tr>
<tr>
<td><strong>Precision @5</strong></td>
<td>0.0395</td>
<td>0.0921</td>
<td>🔼 +133%</td>
<td>추천 아이템 중 실제 관심 아이템 비율 증가</td>
</tr>
<tr>
<td><strong>MRR @25</strong></td>
<td>0.1216</td>
<td>0.2752</td>
<td>🔼 +126%</td>
<td>첫 번째 정답 아이템이 상위권에 더 잘 위치</td>
</tr>
<tr>
<td><strong>Coverage</strong></td>
<td>0.2509</td>
<td>0.4299</td>
<td>🔼 +71%</td>
<td>더 많은 아이템이 추천에 포함 (다양성 증가)</td>
</tr>
</tbody></table>
<p>구매라는 데이터가 커머스에서는 다른 이벤트에 비해서 sparse한 데이터인데, 이를 보충할 수 있는 행동 이벤트(장바구니, 좋아요, 조회)를 통해서 유저의 선호 판단이 이전보다 향상 되었다고 생각합니다.</p>
<p>또한 사용중인 레시핀인 Personalize-v2는 Transformer 기반 모델로 구매 뿐만 아니라 조회, 장바구니, 좋아요 등의 행동을 통해서 행동 패턴(맥락)을 파악하여 관심사를 추론하기 때문에 행동 데이터 추가시에 성능의 향상이 일어났다고 생각합니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/ac13e747-3df9-42a9-ac01-530b222f5892/image.png" alt=""></p>
<h3 id="마치며">마치며</h3>
<p>처음에는 생소한 환경에서, 낯선 데이터들을 보며 어떻게 추천 시스템을 구축할 수 있을지 걱정이 앞섰습니다. 하나씩 해나가자는 마음으로 천천히 데이터 베이스를 뜯어보고, 코드를 보면서 현재 상황을 파악하는 일은 쉽지 않았습니다. 하지만 하나씩 해나가면서 자신감이 조금씩 붙기 시작했습니다. 오래도록 관리되지 않은 먼지 쌓인 GA 데이터와 Bigquery 테이블을 새롭게 단장하고 다시금 동작하도록 기름칠하고 새롭게 파이프라인을 구축했을 때는 청소를 깔끔히 한 뒤의 뿌듯함과도 같았습니다. 또한 Cursor, Claude Code, GPT의 발달로 막막한 부분에 대해 도움을 받을 수 있어 작업 효율이 올라간 것을 체감할 수 있었습니다 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[글또 9기 회고 ]]></title>
            <link>https://velog.io/@hong7_/%EA%B8%80%EB%98%90-9%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@hong7_/%EA%B8%80%EB%98%90-9%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 14 Apr 2024 14:03:30 GMT</pubDate>
            <description><![CDATA[<p>글또 9기의 마지막 주에 도달하여, 지난 글또 9기를 진행하며 있었던 일들을 회고하고자 합니다 :)<br>회고는 4L회고 형식에 맞추어 다음과 같은 순서로 진행하고자 합니다 
</br></p>
<pre><code>Liked : 좋았던 점은 무엇인가?
Lacked : 아쉬웠던 점, 부족한 점은 무엇인가?
Learned : 배운 점은 무엇인가?
Longed for : 앞으로 바라는 것은 무엇인가?
</code></pre><hr>
<h3 id="liked">Liked</h3>
<ul>
<li>글을 지속적으로 쓸 수 있는 환경을 만든 것 </li>
<li>블로그 포스팅을 진행하게 되면서 중간 중간 나에게도 정리 시간이 된 것 </li>
<li>같은 직군의 다른 분들을 만나고 고민을 나눌 수 있는 환경이 생긴 것 </li>
<li>관심있는 분야에 대해서 스터디를 진행하고, 스터디원들과 좋은 관계를 유지할 수 있게 된 것 </li>
<li>다른 분들의 활동을 보면서 자극을 받아 동기부여를 할 수 있었던 것 </br>

</li>
</ul>
<h3 id="lacked">Lacked</h3>
<ul>
<li>글 제출 마감일에 임박하여 글을 쓰게된 점 </li>
<li>최초 목표보다 다른 분들의 포스팅에 피드백을 달지 못했던 점 <br>

</li>
</ul>
<h3 id="learned">Learned</h3>
<ul>
<li>지속적으로 무엇인가 만들기 위해서는 환경을 만들고 파이프라인이 될 수 있도록 작업을 단순화, 패턴화하는 것이 중요하다 </li>
<li>같은 직군의 종사하는 분들과의 대화만으로도 배울 수 있는 점들이 많다. </li>
<li>글을 쓸 때 명확하고 확실한 표현(ex. &#39;<del>같다&#39; 보다는 &#39;</del>다&#39;)을 사용하여 문장을 구성할수록 글의 의도가 잘 전달된다 <br> 

</li>
</ul>
<h3 id="longed-for">Longed for</h3>
<ul>
<li>성취하고 싶은 것이 있을 때 더 쉽게 목표에 도달할 수 있도록 환경을 조성하도록 해야겠다 </li>
<li>글 마감 기한과 같은 중요한 일이 있을 때 &#39;n일 전 리마인드&#39;를 명확히 해야겠다 </li>
<li>글 쓰는 과정을 통해 내 생각을 객관적인 시각에서 바라볼 수 있도록하고, 논리적인 전개가 되고있는지 파악하기 </li>
</ul>
<hr>
<p>글또를 진행하면서 가장 중요하게 느낀 것을 한 가지 뽑으라면 &#39;환경 조성의 중요성&#39; 입니다. 
글을 지속적으로 쓰고 싶지만 습관이 쉽게 들여지지 않아 글쓰는 습관을 만들어 보자는 취지에서 글또에 가입하게 되었습니다. 주기적으로 글을 내야하는 환경에 속하게 됨으로써 다른 분들의 글이 지속적으로 올라오는 것도 보면서 동기부여도 되고 무엇보다 글을 쓰는 것에 대한 약속과 책임감이 생긴다는 것이 가장 큰 요인이였습니다. </p>
<p>또한 글또라는 커뮤니티 안에서 동일 직군의 종사하는 분들과 이야기를 나누고, 고민하는 지점들을 공유하면서 심적인 안정감을 느끼기도 했습니다. </p>
<p>글또 안에서 제일 잘 한 선택이라고 한다면, 데이터 리터러시 스터디에 참석한 것입니다. 
글을 주기적으로 쓰는 것 뿐만 아니라 성장을 위한 학습에도 지속 가능성을 부여하기 위해 참여하게 되었습니다. 결과적으로 진행했던 업무에 대해서 다른 분들과 이야기를 나누고 피드백을 주고 받을 수 있었던 점과 새로운 관계를 형성하면서 서로에게 힘이 되었다는 것이 굉장히 의미있는 경험이었습니다. </p>
<p>좋은 기획를 제공해주시고 좋은 운영을 통해 많은 분들이 성장할 수 있도록 도와주신 글또 관계자 분들에게 감사인사를 드리며 마치도록하겠습니다 :)  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이미지 생성 Slack Bot 만들기 ]]></title>
            <link>https://velog.io/@hong7_/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1-Slack-Bot-%EB%A7%8C%EB%93%A4%EA%B8%B0-067sx04l</link>
            <guid>https://velog.io/@hong7_/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1-Slack-Bot-%EB%A7%8C%EB%93%A4%EA%B8%B0-067sx04l</guid>
            <pubDate>Sun, 17 Mar 2024 03:21:09 GMT</pubDate>
            <description><![CDATA[<h2 id="개발-과정">개발 과정</h2>
<p>이미지 생성 기술을 사용중, 미드저니와 니지저니와 같이 이미지 생성을 비설치로 도와주는 서비스들을 참고하여 자체적으로 slack bot을 이용해 만들어보면 재미있을 것 같아 약 5일 동안 진행한 프로젝트 입니다. </p>
<p>첫 목표는 slack bot을 이용해 prompt를 그대로 전달하면 이미지를 생성하는 것을 목표로하고, 이 후 한글을 영어로 입력하고 특정 checkpoint모델을 선정하여 default prompt를 관리하여 유저 입력 prompt가 더 checkpoint의 특성을 더 잘가지고 갈 수 있도록 후처리하는 작업을 진행하였습니다. </p>
<p>대략적으로 아래 4개의 story로 나눠보고, 그 안의 여러가지 task를 나눠 관리해보았습니다. </p>
<pre><code>1. slack bot을 만들고, local에서 fastAPI를 이용한 서버를 동작시켜 특정 기능을 수행하는 bot 만들기 
2. 이미지 생성 서버를 local에 별도로 두고(성능, 비용 문제로 인해) local API서버와 이미지 생성 서버의 통신을 통해 이미지 저장 및 관리 
3. api 서버를 aws EC2를 이용해 배포해보기
4. 추가 기능인 한글 -&gt; 영어 번역, 특정 prompt 소거, 에러 핸들링 </code></pre><p>첫 개발시에 글또의 또봇 코드를 보면서 많이 참고할 수 있었습니다. 감사합니다 :) 
<br></p>
<h2 id="slack-bot-사용-어떻게-slack-bot은-내가-짠-로직을-수행시킬까">Slack Bot 사용 (어떻게 Slack Bot은 내가 짠 로직을 수행시킬까?)</h2>
<p>slack bot을 만들고, 간단하게 동작하도록하는 자료는 다른 분들이 상세하게 설명해주신게 많아 넘어가고자 힙니다. </p>
<p>다만, 저는 처음에 slack bot에서 slash command 혹은 특정 명령을 수행하는 동작 과정에 흥미가 갔었습니다. 분명 bot 자체는 slack server에서 관리할텐데 어떻게 내부 로직은 사용자가 만든 서버에서 작동시키도록 할 수 있는지가 궁금했습니다. </p>
<p>물론 그 중에서도 slack bot 통신 방법 중 websocket모드가 있는데 특정 url을 사용하지 않고도 slack ui에서 사용자가 발생시킨 action을 slack에서 서버에서 api서버를 찾아 보내준다는 것이 놀라웠습니다. </p>
<p>가설 중 하나는, 서버가 켜짐과 동시에 여러 init과정 중에 api서버가 가지고 있는 통신으로 slack server와 통신이 이뤄지고 slack에서는 해당 public ip를 관리하여 통신하는 방식이 아닐까 했습니다. </p>
<p>이에 따라, 리서치를 진행하고 결과적으로 GPT가 정리한 내용은 다음과 같습니다 </p>
<pre><code>Slack AsyncApp 통신
AsyncApp 인스턴스 생성: AsyncApp 인스턴스는 Slack 이벤트와 상호작용을 위한 주요 객체입니다. 
이 인스턴스는 Slack Bot 토큰을 사용하여 Slack API와의 인증 및 상호작용을 처리합니다.

AsyncSocketModeHandler 인스턴스 생성: AsyncSocketModeHandler는 AsyncApp 인스턴스와 APP_TOKEN을 인자로 받습니다. 
이 핸들러는 Socket Mode를 사용하여 Slack과의 연결을 관리하고, Slack으로부터 실시간 이벤트를 받습니다. 
여기서 APP_TOKEN은 Socket Mode에서 사용되는 특별한 슬랙 앱 토큰(Slack App-Level Token)으로, 이 토큰은 WebSocket 연결을 위한 인증에 사용됩니다.

연결 및 이벤트 리스닝 시작: AsyncSocketModeHandler 인스턴스에 의해 Slack과의 WebSocket 연결이 초기화되고, 이 연결을 통해 실시간으로 이벤트를 수신하기 시작합니다. 
이때, 서버는 Slack으로부터 오는 데이터를 실시간으로 처리할 수 있게 됩니다.

이벤트 처리: Slack으로부터 오는 이벤트는 AsyncApp에 의해 처리됩니다. 
개발자는 다양한 이벤트 타입에 대해 콜백 함수를 정의할 수 있으며, 이러한 콜백 함수는 특정 이벤트가 발생했을 때 실행됩니다.</code></pre><p>이를 바탕으로 실제 코드를 도식화 하면 다음 과정을 통해 slack event에 대한 listening이 시작된다고 이해했습니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/45f427fd-4502-4c06-af86-963a94fb4921/image.png" alt=""></p>
<p>그렇다면 여러 서버에서 init하게 되면 어떻게 동작할까 궁금하여 시도해봤을 때, websocket방식은 여러 서버에서 init이 발생했을 때 제일 마지막에 init된 서버를 기준으로 통신하게 됩니다 </p>
<h4 id="slack-handler">slack handler</h4>
<p>SlackService라는 class를 만들어, 처음 fastapi가 실행될 때 미들웨어에 주입하는 방식으로 의존성 주입을 사용했습니다 </p>
<pre><code>@app.middleware
async def inject_service_middleware(req: BoltRequest, next: Callable) -&gt; None:
    req.context[&quot;service&quot;] = SlackService()
    await next()
    return</code></pre><p>미들웨어 의존성 주입 이유 또한 GPT가 정리한 내용으로 보면 다음과 같습니다 </p>
<pre><code>middleware 의존성 주입 이유
서비스의 재사용성: lackService와 같은 서비스 인스턴스를 요청 컨텍스트에 주입함으로써, 
애플리케이션의 다른 부분에서 해당 인스턴스를 쉽게 재사용할 수 있습니다. 
이는 코드 중복을 줄이고, 서비스 로직의 일관성을 유지하는 데 도움이 됩니다.

의존성 관리: 의존성 주입은 의존성 관리의 한 형태로, 각 요청이 처리될 때 필요한 의존성(이 경우 SlackService)을 자동으로 주입함으로써, 
컴포넌트 간의 결합도를 낮추고, 유지 보수성 및 테스트 용이성을 향상시킵니다.

중앙 집중화된 설정: 모든 요청에 대해 공통된 설정이나 초기화 로직(예: 로깅, 서비스 인스턴스 생성, 요청 검증 등)을 실행하고 싶을 때 미들웨어를 사용하면, 이러한 로직을 중앙 집중적으로 관리할 수 있습니다. 
이는 애플리케이션의 구조를 깔끔하게 유지하는 데 도움이 됩니다.

성능 최적화: 특정 서비스 인스턴스를 요청 컨텍스트에 한 번만 생성하고, 
이후의 요청 처리 과정에서 재사용함으로써, 불필요한 객체 생성 오버헤드를 줄이고 애플리케이션의 성능을 최적화할 수 있습니다.
</code></pre><br>

<h2 id="이미지-생성-comfy-ui와-어떻게-통신해서-이미지를-가져올까">이미지 생성 (Comfy UI와 어떻게 통신해서 이미지를 가져올까?)</h2>
<p>이미지 생성에는 <a href="https://www.internetmap.kr/entry/Stable-Diffusion-via-ComfyUI"><em>Comfy UI</em></a>를 사용했습니다. comfy ui를 사용해보면서 comfy ui자체에서도 이미지를 생성해주는 과정과 생성된 이미지를 가져와서 보여주는 방식이 있을 것이라고 생각하여 네트워크 통신을 보게 되었습니다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/56d7f480-53fd-496b-8760-9ad0069fa473/image.png" alt=""></p>
<p>queue를 작동시켰을 때, prompt라는 객체가 생기는 것을 볼 수 있는데 이 객체가 전체 comfy ui의 workflow를 담고있다는 것을 알 수 있습니다. 따라서 사용자가 해당 object안에 내용만 잘 채워넣는다면 ui없이도 이미지를 생성하는 API를 만들 수 있다고 판단했습니다. 이에 따라 리서치를 시작해보고 다음 코드를 참고하여 web socket통신을 통해 구현할 수 있음을 알게되었습니다 
참고 코드 : <a href="https://github.com/itsKaynine/comfy-ui-client">https://github.com/itsKaynine/comfy-ui-client
</a></p>
<p>이에 따라 이미지 생성쪽은 websocket 방식을 채택하여 구현하였습니다. 로컬 이미지 생성 서버와 연결 뒤에 listening하여 특정 이베트에 따라 핸들링하였습니다. </p>
<p>특히 이미지를 얻는 부분에 있어서는 들어오는 객체의 </p>
<pre><code>message = await websocket.recv()  
message = message.replace(&#39;null&#39;, &#39;None&#39;)
message_dict = eval(message)

if message_dict[&#39;type&#39;] == &#39;executed&#39; ~ 
</code></pre><p>인 부분을 이용하여 처리하였고, 이미지를 불러오는 것 또한 comfy ui에서 네트워크 통신을 참고하여 다음과 같이 가져오도록 했습니다 </p>
<pre><code>filename = message_dict[&#39;data&#39;][&#39;output&#39;][&#39;images&#39;][0][&#39;filename&#39;]
subfolder = message_dict[&#39;data&#39;][&#39;output&#39;][&#39;images&#39;][0][&#39;subfolder&#39;]
output_type = message_dict[&#39;data&#39;][&#39;output&#39;][&#39;images&#39;][0][&#39;type&#39;]

image_url = await ImageGenerateRouter.get_image_url_from_generator_server(
    filename,
    subfolder,
    output_type
 )

 async with httpx.AsyncClient() as client:
     response = await client.get(image_url)</code></pre><br> 

<h2 id="결과적으로-slack-event-handler와-websocket-handler">결과적으로 slack event handler와 websocket handler</h2>
<pre><code>@app.on_event(&quot;startup&quot;)
    async def startup():
    # lifespan - 인스턴스 생성시 실행 함수
    # 슬랙 소켓 모드 실행 - handler 설정
    loop = asyncio.get_event_loop()
    if not loop.is_running():
        asyncio.run(message_handler())
    else:
        loop.create_task(message_handler())
    await slack_handler.connect_async()</code></pre><p><img src="https://velog.velcdn.com/images/hong7_/post/ee75bad4-d071-4b2b-b1d9-d9d51b6d5965/image.png" alt=""></p>
<h2 id="1차-결과물">1차 결과물</h2>
<p>&lt;slash command 입력&gt; 
<img src="https://velog.velcdn.com/images/hong7_/post/57e199e4-c4b2-444d-8119-08284ac6fe49/image.png" alt=""></p>
<p>&lt;이미지 생성 중 알림&gt;
<img src="https://velog.velcdn.com/images/hong7_/post/791c4a4f-b11e-4c57-ad8c-3accee7c22fd/image.png" alt=""></p>
<p>&lt;이미지 업로드&gt;
<img src="https://velog.velcdn.com/images/hong7_/post/60eb5c8d-e4ab-4de5-bfd7-b4f5c2600da2/image.png" alt=""></p>
<br> 

<h2 id="추가-기능-개발">추가 기능 개발</h2>
<p>사용성을 위해서 한글로 프롬프트를 입력할 수 있도록 번역 기능을 추가하였고, 이는 google translate api를 사용하였습니다. 또한 nsfw라는 negative prompt를 넣어도 부적절한 이미지가 생성되어 특정 언어를 필터링하는 기능도 만들게 되었습니다. </p>
<p>입력된 prompt 혹은 문장을 split하여 하나씩 검사하는 방식보다는 set을 통해서 forbidden keyword를 추출하여 이를 제거하는 방식을 채택하였습니다 </p>
<pre><code>split_text = re.split(&#39;,\s*|\s+&#39;, translated_text)
identified_forbidden_words = set(forbidden_words).intersection(split_text)

filtered_text = translated_text or word in identified_forbidden_words:
filtered_text = filtered_text.replace(word, &quot;&quot;)</code></pre><p>또한 checkpoint에 따라 유저가 신경쓰지 않아도 이미지 퀄리티를 올릴 수 있도록 default prompt를 구성하고 env파일을 통해 관리하였습니다 </p>
<br> 

<h2 id="서버-구성">서버 구성</h2>
<p><img src="https://velog.velcdn.com/images/hong7_/post/579124ab-31ca-4d24-93d1-223b5316ad63/image.png" alt=""></p>
<h2 id="최종-결과물">최종 결과물</h2>
<p><img src="https://velog.velcdn.com/images/hong7_/post/4834a855-a32e-47e3-ac1e-50d323d826f4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/a86b7e0c-d421-4c95-82cd-f858784ad0c0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/0edf3faa-4dbc-4637-891c-63aca8935a41/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AUTOMATIC1111 을 이용해 Stable Diffusion 맛보기 ]]></title>
            <link>https://velog.io/@hong7_/AUTOMATIC1111-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-Stable-Diffusion-%EB%A7%9B%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/AUTOMATIC1111-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-Stable-Diffusion-%EB%A7%9B%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 18 Feb 2024 13:19:12 GMT</pubDate>
            <description><![CDATA[<p>최근 생성형 AI 기술을 이용해 어떤 서비스 혹은 기능을 개발할 수 있을지에 대해 고민중에 있습니다. 그 과정에서 기술에 대한 깊은 이해에 앞서 해당 기술를 빠르게 사용해볼 수 있는 방법에 대해 공유하고자 합니다</p>
<p>그 중에서도 이미지 혹은 텍스트 기반 이미지 생성 기술인 Stable Diffusion을 많은 리소스를 들이지 않고 경험해 볼 수 있는 WebUI 방식인 Autiomatic1111에 대해서 소개하고자 합니다 </p>
<h3 id="automatic1111-설치">AUTOMATIC1111 설치</h3>
<hr>
<p>AUTOMATIC1111은 local에서 쉽게 Stable Diffusion모델 들을 web ui 를 이용해 사용해 볼 수 있도록 합니다. </p>
<p>python 3.10 버전과 git을 설치했다면 이 후 <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui"><strong>해당 레포지토리</strong></a>를 clone 합니다 
저의 경우 mac을 사용하였기 때문에 <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon"><strong>mac 설치 가이드 과정</strong></a>을 따랐습니다 </p>
<p>이후 해당 폴더로 이동하여 mac의 경우 </p>
<pre><code>./webui.sh </code></pre><p>를 실행시켜 필요한 설치를 진행하면 기본적인 준비는 완료됩니다. 
<img src="https://velog.velcdn.com/images/hong7_/post/b4de7fc0-813a-415c-8acc-9356388e44d8/image.png" alt=""></p>
<p>설치가 완료되면 <a href="http://127.0.0.1:7860/">http://127.0.0.1:7860/</a> 의 주소로 webui 실행됩니다 
<img src="https://velog.velcdn.com/images/hong7_/post/3b74b245-1db6-487a-a9fd-4436cc23ea0d/image.png" alt=""></p>
<h3 id="모델-탐색-및-설치">모델 탐색 및 설치</h3>
<hr>
<p>대표적은 <a href="https://civitai.com/"><strong>Civitai 사이트</strong></a>를 이용해 쉽게 모델을 다운받아 적용해볼 수 있습니다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/e864bb1d-6c7a-45cf-8763-cda50889b21e/image.png" alt=""></p>
<p>해당 사이트에서 Models 탭에 들어가 rate순으로 정렬하게되면 사람들에게 가장 좋은 평가를 받는 모델들 순으로 볼 수 있습니다. 여기서 모델별로 checkpoint, LoRA 등의 용어를 볼 수 있습니다. </p>
<p>checkpoint는 이미지를 생성하는 주모델이고, LoRA, embedding등은 이미지 생성을 위한 보조모델의 기능을 합니다 </p>
<p>따라서 사용을 위해서는 원하는 checkpoint모델을 우선적으로 정하고 이를 더 잘 사용하기 위한 보조모델들을 설치하는 과정을 반복하게 됩니다 </p>
<p>필요한 모델을 다운받은 후에는 이를 사용하기 위해서 올바른 경로에 옮겨주어야 합니다 
checkpoint의 경우 </p>
<pre><code>stable-diffusion-webui/models/Stable-diffusion </code></pre><p>경로에 넣어주어야 webui실행시에 인식이 됩니다 
<img src="https://velog.velcdn.com/images/hong7_/post/fde26103-0d50-4b59-ade8-f2592dc1b747/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/505cdd35-34c4-483c-a546-1db9c7cb91e3/image.png" alt=""></p>
<p>checkpoint가 아닌 LoRA모델을 다운받았을시애는 models 내부에 있는 Lora 디렉토리 하위에 위치시켜주면 됩니다 </p>
<h3 id="모델-사용해보기">모델 사용해보기</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/5dd201a0-d8d9-4927-a21a-b1b7b7cafdb8/image.png" alt=""></p>
<p>상단에 2가지 종류의 프롬프트가 존재하는데, Prompt에는 만들고 싶은 이미지의 특징들을 입력하고 Negative Prompt에는 어떤 특징이 나타나지 않았으면 하는지에 대해서 입력하게 됩니다 </p>
<p>명령어 형식은 아니지만, 다른 사람들이 생성한 이미지는 어떤 프롬프트를 사용해 만들어졌는지를 참고하여 작성해볼 수 있습니다 </p>
<p>사진 하단에 있는 i(info)를 클릭하게되면 해당 이미지는 어떤 Promt를 이용해서 만들어졌는지 알 수 있습니다 
<img src="https://velog.velcdn.com/images/hong7_/post/6439fa40-d9bf-4e17-baeb-10b518566d96/image.png" alt=""></p>
<p>여기서 소괄호 ()는 강조를 의미하고 해당 특징에 가중치를 주는 개념입니다. 그 안에 :(number)의 경우 얼마 정도의 가중치를 줄지에 대해서 설정할 수 있습니다 </p>
<p>저의 경우에는 고3시절에 &#39;가고싶은 학교를 다니는 나의 모습을 상상해봐라&#39; 라는 말들을 듣곤 했는데 여기서 아이디어를 착안하여 &#39;가고 싶은 대학의 과잠바를 입고있는 나의 모습 혹은 신입생 때 나의 모습&#39; 의 이미지를 생성해보면 어떨까라는 시도를 해보았습니다 </p>
<p>Stable Diffusion을 이용해서 만들어본 가상 인물의 이미지 예시 입니다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/54c13571-742a-4edf-baab-fd12a6db5bcb/image.png" alt="">
<img src="https://velog.velcdn.com/images/hong7_/post/4eac0ae9-011d-4d97-9ec6-e92214565dce/image.png" alt=""></p>
<h3 id="추가-기능들">추가 기능들</h3>
<p>상단 Extension 탭에 들어가게 되면 유용한 extension들을 설치하여 Prompt로는 완벽하게 컨트롤하지 못한 부분들을 다룰 수 있습니다 </p>
<p>예를 들어, inpaint 기능을 이용해 이미지 특정 부분만을 재생성할 수 있는데 여기에 roop를 이용해 해당 부분에 내가 원하는 이미지를 기반으로 재생성해볼 수도 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d2448202-5bcf-4042-a5f9-2831785abac4/image.png" alt=""></p>
<h3 id="배운점">배운점</h3>
<hr>
<p>새로운 기술을 사용할 떄, 원리 이해에 앞서 만들어진 기술을 사용해보면서 익히는 것이 얼마나 초반 작업을 빠르게 진행시킬 수 있는지에 대해 배우게 되었습니다. 생성형 AI의 경우 특히 해당 기술을 쉽게 사용해볼 수 있는 인터페이스를 찾아보고 기술을 이용해서 2차적으로 생산해낼 수 있는 컨텐츠가 무엇인지 고민하는 것이 중요한 지점임을 배웠습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나는 어떻게 팀원들에게 도움이 될 수 있을까?]]></title>
            <link>https://velog.io/@hong7_/%EB%82%98%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%8C%80%EC%9B%90%EB%93%A4%EC%97%90%EA%B2%8C-%EB%8F%84%EC%9B%80%EC%9D%B4-%EB%90%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@hong7_/%EB%82%98%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%8C%80%EC%9B%90%EB%93%A4%EC%97%90%EA%B2%8C-%EB%8F%84%EC%9B%80%EC%9D%B4-%EB%90%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sat, 20 Jan 2024 14:14:40 GMT</pubDate>
            <description><![CDATA[<h3 id="개인적인-불만일까-해결해야할-문제일까">개인적인 불만일까 해결해야할 문제일까?</h3>
<hr>
<p>팀원들과 업무 회고, 티타임을 진행하다보면 공통적인 &#39;불만 사항&#39;을 발견할 때가 있다. 불만 사항이 지속된다면 팀원들의 만족도는 떨어질 것이고, 만족도가 떨어진다면 적극성이 떨어지며 퇴사, 목표 달성 실패가 발생하고 이는 팀 전체에 급속도로 퍼지게 되어 긍정적인 미래를 기대하기 힘들다. 따라서 &#39;공통적으로 발생하는 불만 사항, 반복적으로 발생하는 불만 사항&#39;은 문제로 정의하고 해결하는 것이 좋다고 생각한다. </p>
<pre><code>&#39;나는 팀에서 어떤 역할을 할 수 있을까, 내가 팀원들에게 어떤 도움이 될 수 있을까?&#39; 
</code></pre><p>라는 질문을 스스로에게 던지곤 하는데, 소통에서 발견한 공통적이고 반복되는 문제를 해결하는 것이 내가 할 수 있는 역할이고 도움 중 하나라고 생각했다. 따라서 문제를 정의하고 솔루션을 만들고 의사결정자를 설득하여 변화를 만들어보자는 목표를 세우게 되었다. </p>
<br> 

<h3 id="문제안에-문제를-찾아서">문제안에 문제를 찾아서</h3>
<hr>
<p>&#39;~ 때문에 너무 힘들다&#39; 라는 말을 들어면 먼저 &#39;왜 힘들까&#39;, &#39;왜 그런 문제가 발생하게 될까?&#39;를 질문하게 된다. 이를 통해서 표면적으로 들어나는 단어와 안에 숨어있는 진짜 문제를 파악하고자 노력한다 </p>
<p>예를 들어 다음과 같다</p>
<pre><code>&#39;업무 저글링이 심해서 힘들어요&#39; 

 - 업무 저글링이 있으면 왜 힘들까요? 
   1. 금방 처리할 수 있는 자잘한 업무 요청이 빈번하게 들어와 스위칭이 자주 발생해서 하나의 문제에 집중하기가 어려워요 
   2. 어떤 업무를 더 우선적으로 처리해줘야할지 혼란스러워요 
   3. 급하게 들어온 업무를 처리하자니 나의 일에 병목이 생기고 급하게 들어온 요청을 뒤로 미루자니 해당 팀에 병목이 생겨요 

 - 업무 저글링은 왜 발생할까요? 
     1. 업무가 큰 단위로 묶여서 들어오지 않고, 작은 단위로 자주 들어와요 
    2. 중간에서 업무를 관리해줄 수 있는 사람이 없어요(타부서에서 업무가 다이렉트로 요청이 와요)
    3. 단순 데이터 추출 업무인데 비개발자가 접근할 수 있는 방법이 없어요 </code></pre><pre><code>&#39;프로젝트가 너무 산발적으로 진행되어서 어떤 목표를 가지고 있는지 모르겠어요&#39; 

 - 왜 힘들까요?
   1. 제가 팀에서 어떤 역할인지 모르겠어요(그냥 기계처럼 업무를 처리하는 것 같아요)
   2. 왜 해야하는지 모르겠어서 동기부여가 점점 떨어져요

 - 왜 그런일이 발생할까요? 
     1. 프로젝트가 문제를 지표로 정의하지 않고 &#39;~기능이 있으면 효과가 있을 것이다&#39;로 시작해서
    2. 전사가 목표로 하는 지표가 불명확해서 지표로 문제 정의가 이뤄지지 않아서 </code></pre><p>이렇게 인터뷰를 진행하고 정리하고 나면 다시 문제를 정의하거나 문제의 범위를 넓히거나 좁히는 작업을 진행하게 되었다. 이를 &#39;본질적인 문제 파악&#39; 과정이라고 생각한다 </p>
<p>위 내용에서 정리한 문제는 다음과 같다 </p>
<pre><code>1. 데이터 추출로 인한 병목과 자동화가 되어있지 않아 비효율이 발생한다. 
2. 지표 기반의 의사결정이 없다.
3. 업무 관리 책임이 불분명하다. </code></pre><br> 

<h3 id="정의한-문제에서-why-시작하기">정의한 문제에서 why 시작하기</h3>
<hr>
<ol>
<li>데이터 추출로 인한 병목과 자동화가 되어있지 않아 비효율이 발생한다 
```
왜 병목이 발생하는가? </li>
</ol>
<ul>
<li>데이터 추출 업무를 특정 인원만 진행할 수 있어서 </li>
</ul>
<p>왜 데이터 추출 업무를 특정 인원만 진행할 수 있는가?</p>
<ul>
<li>비개발 인원이 데이터에 접근할 수 있는 방법이 없어서 </li>
<li>원하는 데이터와 실제 DB에 있는 데이터를 연결짓기 어렵기 때문에 </li>
</ul>
<p>왜 비효율이 발생하는가? </p>
<ul>
<li>일자, 특정 상수 정도만 변경하는 단순 반복적인 요청들의 빈도가 높아서 </li>
<li>자동화가 되어있지 않아 수기로 작업을 진행하는 경우가 생겨서 
```</li>
</ul>
<ol start="2">
<li>지표 기반의 의사결정이 없다.
```
왜 지표를 확인할 수 없는가? </li>
</ol>
<ul>
<li>지표의 변화를 지속적으로 볼 수 있는 환경이 구축되지 않았다 </li>
<li>어떤 지표를 지속적으로 봐야하는지 논의가 되지 않았다 
```</li>
</ul>
<ol start="3">
<li>업무 관리 책임이 불분명하다. 
```
왜 불분명한가? </li>
</ol>
<ul>
<li>업무 관리를 누가 할 것인지 논의되지 않았다 </li>
<li>이슈 레이징이 되지 않았다 <pre><code></code></pre></li>
</ul>
<br>

<h3 id="솔루션-what-to-do-만들기">솔루션 (What to do) 만들기</h3>
<hr>
<p>솔루션은 문제 정의 과정에서 why를 타고 들어간 세부적인 이유를 보면서 생각해볼 수 있다.
또한 문제는 따로 정의했지만, 문제를 해결할 수 있는 방법은 하나일 수도 있다. 예를 들어 1번과 2번은 모두 데이터 추출 및 열람 환경을 구축하여 해결할 수 있다. 이를 위해 별도의 admin 페이지를 만들거나 클라우드 환경을 이용해서 파이프 라인을 구축한 뒤 데이터 마트와 대시보드 시각화를 통해 해결하는 방법이 있다.</p>
<p>대시보드와 함께 아래와 같이 현재 팀이 목표로 하고 있는 주요 지표들과 하위 어떤 세부 지표들이 있고 각 프로젝트가 현재 어떤 지표를 올리기 위한 것인지 파악하기 쉽도록 tree를 만들어 공유할 수도 있다 
<img src="https://velog.velcdn.com/images/hong7_/post/867b99f5-d5fc-413f-8e91-ee78e3f806e0/image.png" alt=""></p>
<p>솔루션 리스트를 작성하고 각 솔루션에 대해서 장점과 단점을 작성한 뒤 의사결정자와 논의를 진행한다.</p>
<h3 id="결과물에-대한-피드백-수집과-단계적-고도화">결과물에 대한 피드백 수집과 단계적 고도화</h3>
<hr>
<p>문제를 해결하면서 배우게 된 것은 &#39;한번에 모든 것을 해결할 수는 없다&#39;이다. 처음부터 완벽하게 모든 것을 만들기보다 핵심적인 것을 먼저 만들어보고 사용성이 좋은지 어떤 것이 부족하고 추가되면 좋을지 피드백 수집 과정도 중요하다. 이를 기반으로 빠르게 적용해서 고쳐야할 부분인지 혹은 장기적으로 고쳐지면 좋은 부분인지를 식별하여 단계적 고도화를 진행했다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소규모 팀에서 데이터 파이프라인 구축 ]]></title>
            <link>https://velog.io/@hong7_/%EC%86%8C%EA%B7%9C%EB%AA%A8-%ED%8C%80%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@hong7_/%EC%86%8C%EA%B7%9C%EB%AA%A8-%ED%8C%80%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Sun, 24 Dec 2023 14:33:34 GMT</pubDate>
            <description><![CDATA[<h3 id="구축-배경">구축 배경</h3>
<pre><code>데이터 요청 상황 

&quot;최근 ~한 유저의 수가 궁금해요&quot; 
&quot;결제 전환율이 궁금해요&quot;
&quot;프로젝트의 성과를 측정하고 싶어요&quot; 
etc..</code></pre><p>처음에는 소규모 팀이라 데이터 팀이 별도로 존재하지 않고, 위와 같이 들어오는 요청들을 그때 그때 리소스를 투자해서 추출해주는 방식으로 데이터를 제공하게 되었다. 그렇게 진행하다보니 크게 다음과 같은 문제점을 식별할 수 있었다 </p>
<pre><code>- 들어오는 데이터 요청에는 명확한 기준과 정의가 없다 
- 어떤 목적으로 요청되는 데이터인지 맥락 공유가 되지 않는다 
- 요청시마다 대응하는 팀원이 달라 코드가 쉽게 휘발되고, 하나의 기준으로 데이터가 관리되지 않는다 </code></pre><p>위 3가지를 관통하는 내용이 결국은 <strong>&quot;팀의 데이터 기준이 없다&quot;</strong> 이다. 
*<em>예를 들어, &quot;결제 전환율&quot;은 &#39;신규 유저의 결제 전환율인지&#39;, &#39;재결제 유저의 전환율인지&#39; 
신규 유저라면 신규 유저의 정의나 기준은 무엇인지, 전환율이라면 분모는 무엇인지 *</em>가 정의되어 있지 않았다.</p>
<p>또 한 가지는 &#39;데이터를 어떻게 전달할 것인가?&#39;의 문제였다. 
처음에는 개인 DM으로 데이터 요청이 오게되어 데이터도 개인적으로 전달되다보니 팀에 공유되지 않았다 </p>
<p>이 후 채널을 파서 요청을 처리하다보니 지속적으로 똑같은 데이터, 혹은 약간의 변형이 있는 데이터 요청들이 중복된다는 사실을 알게되었다.</p>
<p>중복적으로 혹은 자주 요청되는 데이터에 대해서는 날짜 시간에 맞춰 스크립트를 직접 실행시켜 전달해줘야했다.
무엇보다 채널에 데이터가 쌓이더라도 파편적으로 분산되어있어 데이터를 연관지어 보기 힘들다는 것이다. 이는 데이터를 통해 문제를 파악하고 추가 분석으로 이어지기 보다는 단발적인 호기심 해결이나 사실 전달로 끝나게 되었다.</p>
<br>

<h3 id="문제-정의-및-채택-해결-방식">문제 정의 및 채택 해결 방식</h3>
<p>문제는 크게 다음과 같다 </p>
<pre><code>- 데이터에 명확한 기준이 없어 하나의 지표로 관리되지 않는다.
- 반복적인 요청으로 인해 백엔드 리소스가 분산되고 이로 인해 요청이 줄어들고 관심도가 낮아진다.
- 제공되는 데이터가 하나의 보드(Board)에 모이지 않고 파편적으로 제공되어 찾기 힘들다.</code></pre><p>*<em>1. 데이터에 명확한 기준이 없어 하나의 지표로 관리되지 않는다.
*</em>해당 문제를 해결하기 위해서는 팀이 현재 소통에 자주 사용하는 지표들에 대해서 명확하게 정의 내리는 회의를 주도하게 되었다. </p>
<p> 해당 회의에서는 *<em>해당 지표를 지속적으로 봐야하는 이유는 무엇인지, 서비스에 맞는 정의는 무엇인지를 정했다. *</em> </p>
<p> 예를 들어 다음과 같다</p>
<ul>
<li>간단한 결제 전환율이어도, &#39;온보딩 시작 대비 결제 전환율&#39;, &#39;추천 대비 결제 전환율&#39;로 명확한 시작점을</li>
<li>신규 결제 전환율이라면 신규 유저의 정의는 무엇인지 <ul>
<li>Retention의 이벤트는 어떤 것으로 할 것인지, 해당 이벤트가 발현했다고 볼 수 있는 기준은 무엇인지</li>
<li>Retention을 어떤 방식으로 볼 것인지 (Classic/Range/Rolling)<blockquote>
<p>중요한 것은 이런 지표를 정의하는 과정에서 그럼 해당 지표의 목표는 무엇이고 왜 그런 목표를 잡아야 하는지 논의가 일어날 수 있다는 것이였다. </p>
</blockquote>
</li>
</ul>
</li>
</ul>
<p>*<em>2. 반복적인 요청으로 인해 백엔드 리소스가 분산되고 이로 인해 요청이 줄어들고 관심도가 낮아진다.
*</em>백엔드 작업자가 자신의 메인 작업 이외에 갑자기 들어오는 요청을 처리하기 위한 업무 스위칭으로 인해 깨지는 집중력이 문제였다. 따라서 처음에는 다음과 같이 해결했다 </p>
<pre><code>  &#39;백엔드 서버에 스크립트 작성&#39; -&gt; Firebase로 주기적으로 요청 -&gt; &#39;S3에 저장&#39; -&gt; Firebase + Slack bot을 이용해 특정 채널에 업로드 </code></pre><p>그러나 여러 개의 스크립트로 분산되고 결과물이 <strong>3.하나의 보드로 합쳐지는게 아니라 분산적으로 채널에 올라오는</strong> 문제가 여전히 남아있었다. </p>
<p><strong>3. 제공되는 데이터가 하나의 보드(Board)에 모이지 않고 파편적으로 제공되어 찾기 힘들다.</strong>
처음에는 Pyplot 이나 Pandas를 사용해서 이미지 파일, 엑셀 파일로 만들어서 처리했지만 이를 하나의 보드로 관리할 수가 없었고 매번 채널에서 파일을 찾아서 봐야하는 수고로움이 생긴다. 
이런 과정의 Depth를 줄이고 지속적으로 데이터가 스케쥴링으로 인해 처리되는 것을 중심으로 찾다보니 &#39;데이터 파이프라인&#39;의 선택지를 고려하게 되었다. </p>
<p>파이프 라인 안에서도 실시간/배치 파이프라인 중 배치 파이프라인을 선택했다. 소규모 팀으로 실시간으로 누적되는 지표를 보기보다는 반나절, 혹은 1일 단위의 업데이트로 충분하기 때문이다. 배치 파이프라인 구축으로는 GCP Composer를 이용한 Airflow를 선택했다. 선정 이유는 GCP 크래딧이 있어 0원의 비용을 들이고 1년 이상 운영할 수 있었고 클라우드 환경에 구축하기 때문에 로컬 환경에 별도의 Airflow를 위한 환경을 구축하지 않아도 된다는 이점이 있기 때문이다. </p>
<p>결과적으로 GCP 생태계 안에서 Airflow, Bigquery, LookerStudio를 사용해서 데이터 배치 처리 및 시각화를 해결하게 되었다. 이로 인해 고정적인 보드 하나로 지표를 확인 및 관리할 수 있는 환경을 구축하게 되었다. 
<img src="https://velog.velcdn.com/images/hong7_/post/bcea9cc4-eba9-4850-a496-21bdc334592e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/70e08391-cefd-4b8c-872e-57de98779dc0/image.png" alt=""></p>
<h3 id="마주한-기술적-문제">마주한 기술적 문제</h3>
<ul>
<li><p>source database에서 Bigquery까지 어떤 방식으로 데이터를 옮길 것인지 
  이는 간단하게 구글링을 통해서 해결하거나, GPT에게 물어보는 방식을 사용했는데 GPT는 실제로 존재하지 않는 Operator를 말해주는 경우가 많아서 해당 답변을 기반으로 구글링을 진행했다. </p>
</li>
<li><p>source database에서 데이터를 추출하고 Bigquery에 적재할 때 APPEND, TRUNCATE 방식 중 어떤 것을 사용할 것인지 
 기존 백엔드 모델링시에 일부 모델은 created_at, updated_at과 같이 시간을 이용한 조건 검사를 할 수 있는 필드가 없어서 전체를 dump하는 방식인 TRUNCATE를 사용하고 별도로 update 없이 create로 누적되는 로그의 경우에는 Airflow 내부에 {{ ds }}를 사용해서 시간을 이용한 조건 검사를 통해 APPEND 하는 방식을 채택했다. </p>
<ul>
<li>과거 데이터 백필은 어떻게 할 것인지 
나의 경우에는 처음에는 모든 데이터를 TRUNCATE하는 방식의 백필 DAG을 만들고 쓰고난 뒤에는 비활성화 시켰다. 이 후 메인 DAG에서는 APPEND를 사용해서 데이터를 누적해나가도록 했다 </li>
</ul>
</li>
<li><p>Bigquery 내부의 데이터셋은 어떤 기준으로 나눌 것인지 
  데이터 마트를 구성한다고 하기에는 소스가 RDB와 Amplitude밖에 없었지만, 팀마다 실제 요청하는 데이터의 종류가 달랐다. Bigquery를 DataWarehouse 이지 Mart로 사용하였고 RDB의 모델링 그대로 먼저 Bigquery에 올렸고 Amplitude는 별도 코드 작성없이 Connection 설정을 통해 데이터를 받을 수 있었다(물론 Merged Id는 따로 처리해줘야했다) </p>
<p>  이 후 수집된 데이터에 대해서 용도에 따라 새로운 테이블을 정의하고 관리했다. 예를 들어 유저의 메타 정보들을 관리하는 user table, 상품 별 추천, 판매, 재고현황을 볼 수 있는 sku_table, 대시보드 시각화를 위한 각 종 테이블을 모델링하여 관리하였다. </p>
</li>
<li><p>테이블을 새롭게 만들고 관리할 때 필요한 테이블에 유효한 값이 있는지 확인할 수 있는지 
  데이터의 신뢰성을 위해서는 가공에 사용되는 테이블들에 정상적으로 데이터가 올라왔는지 확인할 필요가 있었다. 이를 위해서 Sensor에 대해 리서치하고 Sensor Operator를 상속받아 원하는 조건을 처리하도록 하였다. </p>
</li>
<li><p>중복 코드 관리 
  반복적으로 사용되는 경우 하나의 Operator로 별도로 만들어서 관리했다. 예를 들어 
PostgresToGCSOperator, GCSToBigQueryOperator, BigQueryExecuteQueryOperator 각각을 매번 반복해서 사용하기 보단 3개의 Operator를 이용한 PostgresToBigqueryOperator를 새롭게 정의하여 사용했다. </p>
</li>
</ul>
<h3 id="배운점">배운점</h3>
<p>처음에는 데이터 요청에 대한 리소스 분산 문제로 시작되었지만 결국에는 <strong>&#39;팀 내 데이터 기준 정의&#39;, &#39;지표 목표 설정&#39;</strong>이라는 근본적인 문제를 건드리게 되었다. 어떤 일이든 <strong>목표를 명확히해야 현재 상황과의 비교로 문제가 있는 부분을 파악하고 목표를 달성하기 위한 방식을 탐구</strong>하게 된다. 파이프라인을 구축하고 대시보드를 만드는 과정에서 결국 팀의 목표 ROAS달성을 위해서 각 종 지표의 목표는 무엇이고 목표를 달성하기 위한 단계적인 목표는 무엇인지 고민하는 시간을 갖게 되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 기반 의사결정 문화 도입기]]></title>
            <link>https://velog.io/@hong7_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%B0%98-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EB%AC%B8%ED%99%94-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B8%B0%EB%B0%98-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EB%AC%B8%ED%99%94-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Sun, 10 Dec 2023 07:52:12 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="데이터-기반-의사결정">데이터 기반 의사결정?</h3>
<pre><code>데이터 기반 의사 결정(Data-driven decision-making)이란 
지표 및 데이터를 사용하여 조직의 목표에 부합하는 전략적 비즈니스 의사 결정을 내리는 것을 의미한다</code></pre><p>이 사전적 표현을 실제 경험한 후 나의 방식대로 표현하면 다음과 같다. </p>
<pre><code>데이터 기반 의사결정은 팀에서 데이터를 활용하여 
&#39;문제 정의&#39; -&gt; &#39;가설 및 Ideation&#39; -&gt; &#39;A/B testing&#39; -&gt; &#39;Lesson Learn&#39; 
의 과정을 통해 Product를 성장시키는 방법 중 하나이다. </code></pre><br> 

<h3 id="왜-데이터-기반-의사결정-문화를-구축하는가">왜 데이터 기반 의사결정 문화를 구축하는가?</h3>
<p>팀의 성장이 정체되었음을 인지하고 팀원들과 커피챗을 하면서 수집한 의견들을 구체화하면 다음과 같다. </p>
<ul>
<li>일부 결과 지표를 보지만, 결과 지표를 보고 논의 하지 않는다. </li>
<li>Action Item을 User Side에서 생각하지 않고 개인의 사용 경험으로부터 제시한다.  </li>
<li>개선하고자 하는 목표 지표가 명확하지 않아 자신이 비지니스에 어떤 영향을 끼치는지 인지하지 못한다. </li>
<li>Product와 User에 대한 이해도가 높지 않다.</li>
</ul>
<p>위와 같은 문제점을 인식하여 리서치와 주변에 자문을 구했을 때 다음과 같은 <strong>본질적인 문제</strong>로 정리되었다</p>
<ul>
<li>팀원들에게 <strong>&#39;생각할 거리&#39;</strong>가 주워지지 않는다 </li>
<li>&#39;<strong>왜?</strong>&#39;라는 질문을 많이 하지 않아 의사결정이 논리적으로 이뤄지지 않는다 </li>
<li>달성하고자 하는** 목표 지표가 명확하지 않고 현재 상황과 목표 지표를 비교**할 수 있는 수단이 없다 </li>
</ul>
<p>&#39;생각할 거리&#39;는 결국 데이터다. 데이터 기반으로 의문을 던지고 이에 가설을 세워 추가적인 데이터를 보고 Action Item을 세운 뒤 실험하고 학습하여 또 다른 실험으로 이뤄질 수 있도록 하기 위해서는 데이터로 시작해서 데이터로 끝나야 한다고 생각하게 되었다. 이렇게 하면 <strong>의사결정이 논리적으로 이뤄지고 목표 지향적인 방향</strong>으로 나아갈 수 있다고 생각한다.</p>
<br> 


<h3 id="어떻게-해결하고자-했는가">어떻게 해결하고자 했는가?</h3>
<ul>
<li><strong>지표 정의</strong> 
대시보드를 만들기 이전에 팀에서 지속적으로 봐야하는 지표는 무엇이고, 해당 지표가 우리 팀에서는 어떻게 정의될 수 있는지를 결정하는 작업이 선행되어야 한다. 하나의 큰 비지니스 목표를 달성하기 위한 작은 목표들로 나눠지고 작은 목표들을 이루기 위해서는 각 지표가 어떤 목표를 이루어야 할지 제시할 것을 의사결정권자인 팀원들에게 요청했다.
<img src="https://velog.velcdn.com/images/hong7_/post/e6a8add9-6b35-4e88-90f7-ef806ffa355c/image.png" alt=""></li>
</ul>
<ul>
<li><strong>데이터 파이프 라인 구축 + 대시보드 배포</strong> 
&#39;생각할 거리&#39;는 시각화된 데이터와 분석 리포트가 그 역할을 할 수 있다. 데이터를 관리하여 스케쥴링을 통해 대시보드를 업데이트 하기 위해서는 파이프 라인을 구축할 필요가 있었다. 나의 경우 GCP를 이용해 Airflow, Bigquery 환경을 구축하고 LookerStudio로 대시보드를 만드는 것이 가장 빠르게 학습하고 적용할 수 있는 방법이였다.
<img src="https://velog.velcdn.com/images/hong7_/post/fd698bf4-c137-47da-b51a-b7ca0aaf11ad/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/216bb879-8fdb-46f3-840f-9fffe2c2a9ba/image.png" alt=""></p>
<ul>
<li>*<em>팀원들에게 Selling하기 *</em>
대시보드를 만들고 배포한 뒤에는 대시보드에서 어떤 정보를 확인할 수 있는지, 분석 리포트를 통해 유저들의 행동 패턴과 문제 상황을 정의하고 이에 따른 가설과 Action Item을 제시하는 자리를 주기적으로 만들었다. 하나의 문화로 만들기 위해서는 1~2번의 발표로 끝나는 것이 아니라 지속적인 노출을 통해서 팀원들에게 스며들어야겠다는 목표로 시작하게 되었다. 이 때 팀원들에게 제시하는 분석 리포트에서 &#39;왜&#39;라는 질문으로 구체화되는 과정을 반복하여 &#39;왜&#39;를 생각하자는 메시지를 전달하였다. 또한 팀 노션에 아카이빙하여 팀원들이 의사결정을 내릴시에 언제든 참고할 수 있도록 하였다. 
<img src="https://velog.velcdn.com/images/hong7_/post/953f43b8-199e-4fd4-b58b-184727025ffd/image.png" alt="">팀원들의 피드백
<img src="https://velog.velcdn.com/images/hong7_/post/8fe4f379-5e36-45b1-adfa-42921bee70ae/image.png" alt=""></li>
</ul>
<ul>
<li>*<em>Action Item 만들기 *</em>
예를 들어 &#39;신규 결제 전환율 n%&#39;라는 목표를 가정했을 때,<ul>
<li>결제까지의 퍼널 중에서 어디서 가장 많은 이탈이 생기는지 파악하고 이탈 유저와 퍼널 통과 유저 사이의 차이점을 분석한다. </li>
<li>&#39;나이&#39;라는 요소가 뚜렷한 차이를 보인다면 해당 페이지에서 금액을 보고 나이가 낮은 유저들은 상대적으로 &#39;구매력이 낮아 이탈 할 것이다&#39;라는 가설을 세운다. </li>
<li>&#39;나이&#39;와 &#39;구매력&#39;이 실제로 관계가 있는지 확인하기 위해 기존 결제 유저를 대상으로 나이대에 따른 결제 전환율과 객단가를 비교한다.   </li>
<li>나이대가 낮은 유저가 실제 객단가와 결제 전환율이 낮다면 &#39;마케팅 주요 타겟에서 나이대가 높은 유저의 유입이 많다록 한다.&#39;, &#39;나이대가 낮은 유저에게는 가격대가 낮은 상품을 위주로 추천한다&#39; 와 같은 Ideation을 제시한다. </li>
<li>제시된 Ideation 중 필요한 리소스와 임팩트를 고려하여 Action Item들을 선정한여 실제 프로젝트가 진행될 수 있도록 한다.   </li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Airflow와 Looker Studio로 대시보드 만들기
]]></title>
            <link>https://velog.io/@hong7_/Airflow%EC%99%80-Looker-Studio%EB%A1%9C-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/Airflow%EC%99%80-Looker-Studio%EB%A1%9C-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 12 Oct 2023 14:12:42 GMT</pubDate>
            <description><![CDATA[<p>데이터를 가치있게 쓰기 방법 중에 하나는 많은 사람들이 데이터에 접근하고 이를 기반으로 생각하고 소통하고 의사결정 하는 것이라고 생각한다. 그러기 위해서는 사용자들이 쉽게 사용할 수 있는 형태로 데이터를 제공해야한다. </p>
<p>팀원들이 데이터에 쉽게 접근하기 위해서는 정제된 데이터를 제공하여 원하는 데이터를 탐색하거나, 요청에 해당하는 데이터를 추출하여 sheet 형태나 대시보드를 구성하여 제공하는 방법이 있다. </p>
<p>Bigquery에 데이터를 수집하고, 이를 가공하여 결과 테이블 혹은 대시보드 형태로 만들었다. 그를 위한 데이터 추출, 변환, 적재의 워크플로우를 Airflow로 관리하였다 </p>
<h3 id="데이터-etl">데이터 ETL</h3>
<hr>
<p>데이터를 추출한 소스로는 RDB(postgres), amplitude였고 RDB의 경우 Airflow를 이용해 ETL을 진행하였고 amplitude의 경우 amplitude의 destination으로 Bigquery를 지정하여 데이터를 가져올 수 있었다. </p>
<p>Airflow의 경우</p>
<ul>
<li><p>data_mart DAG
Postgres → GCS → Bigquery(raw table) → Bigquery(result table)<br>amplitdue의 경우에는 Bigquery(raw table) → Bigquery(result table) </p>
</li>
<li><p>dashboard DAG
Bigquery table sensing → dashboard 를 위한 table 생성(갱신)</p>
</li>
</ul>
<p>의 워크플로우로 관리하였다 </p>
<p>1일 단위로 DAG instance가 생성되도록 하였고, 새벽에 실행되어 팀원들이 출근하면 전날 발생한 데이터까지 처리가 되어 테이블과 대시보드가 제공되도록 하였다.</p>
<p>DAG의 경우에는 기능 단위로 구성하였다. 데이터를 수집하고, 변환, 적재하여 result table을 만드는 data mart DAG과 만들어진 결과 테이블을 sensing하여 조건이 통과되면 대시보드를 만드는 dashboard DAG을 만들었다. sensing의 경우 <code>BaseSensorOperator</code> 를 상속받아 poke부분을 재정의하여 사용하였다. </p>
<pre><code class="language-jsx">class BigquerySqlSensor(BaseSensorOperator):
    ... 

    @apply_defaults
    def __init__(
        self,
        *,
        project_id: str,
        dataset_id: str,
        table_id: str,
        gcp_conn_id: str = &quot;google_cloud_default&quot;,
        impersonation_chain: str | Sequence[str] | None = None,
        deferrable: bool = conf.getboolean(&quot;operators&quot;, &quot;default_deferrable&quot;, fallback=False),
        sql: str,
        location: str,
        **kwargs
    ):
     ... 

        super().__init__(**kwargs)

     ...

    def poke(self, context: Context) -&gt; bool:
        table_uri = f&quot;{self.project_id}:{self.dataset_id}.{self.table_id}&quot;
        self.log.info(&quot;Sensor checks existence of table: %s&quot;, table_uri)
        hook = BigQueryHook(
            gcp_conn_id=self.gcp_conn_id,
            impersonation_chain=self.impersonation_chain,
            location=self.location,
            use_legacy_sql=False
        )

        is_exist = hook.table_exists(
            project_id=self.project_id, dataset_id=self.dataset_id, table_id=self.table_id
        )

                if not is_exist:
                    ...


        if len(res) == 0:
            return False
        else:
            return True</code></pre>
<h3 id="대시보드-만들기">대시보드 만들기</h3>
<hr>
<p>대시보드의 경우 Looker Studio를 사용했다. 데이터 파이프라인을 도입하기 전에는 요청이 들어올 때 마다 스크립트를 작성하고 결과를 xlsx, 이미지 파일(도표)를 제공했었다. 하지만 결과물의 변수를 사용자(요청자)가 직접적으로 제어할 수 없어 시간이 지난 뒤에 재추출을 하거나, 기간을 편하게 옮기면서 탐색하기 쉽지 않았다. 또한 요청을 위한 병목이 발생하였다 </p>
<p>Looker Studio를 사용하여 대시보드를 만들면, 사용자가 원하는 시간에 바로 접속하여 변수를 조작하여 데이터를 탐색할 수 있었고, 이로 인해서 요청으로 생기는 병목과 작업 인터럽트가 줄어들어 업무 효율성이 증가하였다 </p>
<p>대시보드를 만들 때 가장 고민하는 것은 </p>
<ul>
<li>사용자의 니즈가 무엇인가 ⇒ 어떤 정보를 제공해줘야 하는가</li>
<li>대시보드를 구성하기 위해서는 어떤 형태의 결과 테이블을 만들 것인가(어떤 값을 key로하여 테이블들을 조인할 것인지, 어떤 컬럼이 있을지 등)이었다.</li>
</ul>
<p>Looker Studio를 이용하는 것이 처음에는 어색했지만 생각보다 직관적인 사용 방법으로 인해서 빠르게 대시보드를 만들 수 있었다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/960625d5-26d3-48cb-81f7-ddb6805242a6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/a378b848-7ebc-43fa-8a25-d2da00886ae3/image.png" alt=""></p>
<h3 id="대시보드-제공">대시보드 제공</h3>
<hr>
<p>제공의 경우 slack reminder를 사용했다. looker studio를 slack에 연동하여 올리는 방법을 고려했으나 제일 쉬운 방법은 slack reminder를 통해 대시보드의 링크를 정해진 시간에 제공하여 팀원들이 볼 수 있도록 하는 것이였다
<img src="https://velog.velcdn.com/images/hong7_/post/4343fcbf-3c06-41ba-a991-c52e316c2feb/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 파이프라인 구축
]]></title>
            <link>https://velog.io/@hong7_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@hong7_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Mon, 09 Oct 2023 08:06:35 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터-파이프라인">데이터 파이프라인?</h3>
<p>데이터 파이프라인은 데이터를 사용하기 위해서 데이터를 추출(수집), 가공, 적재하는 과정이다. 데이터를 이용하여 매출, 신규/재 결제 전환율, 활성화 유저의 비율 등 의사결정에 필요한 정보들을 팀에 제공할 수 있다. 뿐만 아니라 수집된 데이터를 이용하여 모델을 학습시켜 프러덕트에 사용할 수 있다 </p>
<h3 id="파이프-라인-도입을-생각한-이유">파이프 라인 도입을 생각한 이유</h3>
<p>현재 팀에서는 데이터가 필요할 때 개발자에게 직접적으로 요청하고 이를 스크립트로 처리하여 제공하는 방식을 사용하고 있었다. 일부는 amplitude를 이용하여 데이터 분석을 하기도 했다. amplitude에는 주로 프론트에서 심은 amplitude 이벤트, RDB에 있는 데이터 일부분,  airbridge를 통해서 광고에서 발생된 데이터가 수집된다 </p>
<p>amplitude에서는 주로 퍼널에 대한 이탈율과 A/B 테스트의 결과를 주로 보았다면 그 외에 나머지 데이터 (매출, 리텐션, 코호트 별 결제 전환율, 상품 별 최근 추천수/판매수 등)는 직접 주피터 노트북에서 스크립트를 작성하여 정보를 제공하였고, 이로 인해서 약간의 변화(날짜, 시간에 지남에 따른 재추출)를 원할 때도 요청을 다시 해야하는 비효율이 발생하기도 했다.  </p>
<p>무엇보다 데이터에 대한 접근도가 떨어진다고 생각하였다. 팀원이 데이터에 더 관심을 가지고, 간단한 SQL로 본인이 원하는 데이터를 추출하여 업무에 병목이 없도록 만든다면 데이터 기반 의사결정에 좋은 영향을 줄 것 이라고 생각했다 </p>
<p>이 후 트래픽이 높아졌을 때와 딥러닝 모델의 학습 자동화로의 확장도 고려하였다 </p>
<h3 id="airflow를-이용하여-파이프-라인을-구축하고-대시보드-만들기">Airflow를 이용하여 파이프 라인을 구축하고 대시보드 만들기</h3>
<p>파이프 라인 도입 이후 제일 빠르고 효과 있는 작업 중 하나는 대시보드 만들기라고 생각했다. Airflow를 이용하여 RDB와 amplitude에 있는 데이터를 Bigquery에 raw데이터를 수집하여 가공한 후에 result 테이블로 적재하는 datamart DAG와 만들어진 result 테이블을 이용하여 대시보드에 필요한 테이블을 가공하는 dashboard DAG를 만들었다. </p>
<p>dashboard DAG은 datamart DAG의 작업 결과물인 table을 sensing하여 특정 조건을 만족했을 시에 작업이 수행되어 대시보드 테이블을 갱신하도록 만들었다 </p>
<p>dashboard DAG를 통해 생성된 테이블들은 looker studio를 이용하여 시각화하였고 slack의 리만인더를 통해 구성원들의 출근 시간에 맞게 대시보드의 링크를 업로드하여 1일간 발생한 데이터를 확인할 수 있도록 하였다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/0dbcd44f-deea-4bc4-90b0-770ba06f729e/image.png" alt=""></p>
<h3 id="쿼리-템플릿을-작성하여-사용자가-원하는-데이터를-주도적으로-추출할-수-있도록-하기">쿼리 템플릿을 작성하여 사용자가 원하는 데이터를 주도적으로 추출할 수 있도록 하기</h3>
<p>반복적으로 들어오는 데이터 요청에 대해 요청자가 눈치를 보는(?)것을 없애고 작업의 병목을 없애고자 하였다. Bigquery에 템플릿 쿼리를 작성하고, WHERE절 이하에 일부분을 원하는 조건으로 바꿔 원하는 정보를 얻을 수 있도록 하였다. 이를 통해 팀원들이 Bigquery사용과 SQL사용에 익숙해지면 WHERE절 이하를 수정하여 사용하는 것에서 나아가 주도적으로 데이터를 탐색하고 분석할 수 있을 것이라고 생각한다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/5cb47172-afe3-48cd-b885-cc5815bf4f20/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/35f7add3-4c50-46d4-a7e3-147654f378f7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Numpy Structured Array
]]></title>
            <link>https://velog.io/@hong7_/Numpy-Structured-Array</link>
            <guid>https://velog.io/@hong7_/Numpy-Structured-Array</guid>
            <pubDate>Mon, 07 Aug 2023 11:24:17 GMT</pubDate>
            <description><![CDATA[<p>이전 포스팅에서 numpy array의 빠른 이점에 대해서 알게 되었다.</p>
<p>하지만 실제 프로젝트에서는 여러형태의 data type으로 구성된 list를 다룰 경우가 더 많았다. 이럴 때는 어떻게 numpy array를 사용하여 그 이점을 활용할 수 있을까?</p>
<h3 id="numpy-structured-array">Numpy Structured Array</h3>
<p>만약 여러 data type을 가진 리스트를 numpy array로 만들면 어떤 결과가 나올까?
<img src="https://velog.velcdn.com/images/hong7_/post/13ba8596-6877-4305-9dcd-2f7ec40d14fd/image.png" alt=""></p>
<p>결과적으로, 모두 문자열의 형태로 바뀌는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/6dd6929a-8170-40a4-bc00-e003bc50e997/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/9b3d9d0c-4fa4-41b6-8770-394e025176fc/image.png" alt=""></p>
<p>numpy 공식 문서를 살펴 보면 structured array는 c에서의 구조체(struct)를 모방하여 만들었다는 것을 수 있다. 따라서 여러 데이터를 가진 구조체의 배열 형태로 이해할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/dfdfbfb3-2ef8-4c7a-b548-9aa1bf7b50c8/image.png" alt=""></p>
<p>다음과 같은 형태로, 구조체 안에 리스트가 있을 때는 그 크기를 명시적으로 적어주어야 한다(메모리 상에 크기를 미리 지정하기 위함으로 생각한다)</p>
<h3 id="dtype--object와는-무엇이-다른-것일까">dtype = object와는 무엇이 다른 것일까?</h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/b3e89fd5-fd1d-4d0f-a3e0-dc2306229e48/image.png" alt=""></p>
<p>이렇게 numpy array를 생성하고자 하면 결과에서 dtype을 object로 명시해줘야 한다는 warning을 볼 수 있다.
dtype = object로 지정하면 어떤 일이 발생하는지 궁금하여 찾아보던 중 다음과 같은 답변을 찾을 수 있었다</p>
<p><a href="https://stackoverflow.com/questions/29877508/what-does-dtype-object-mean-while-creating-a-numpy-array"><img src="https://velog.velcdn.com/images/hong7_/post/40c08bb3-28ea-4dd7-8cdb-ae9b8abdda4b/image.png" alt="">
</a></p>
<p>결국에 dtype object를 이용하면 기존의 파이썬이 list를 관리하는 것과 크게 다를 것이 없다는 것이다. 이렇게 되면 결국 Pyobject를 가지게 되는 것으로 numpy의 이점을 사용할 수 없다고 생각한다.</p>
<p>이를 확인해보기 위해서 간단하게 주소값을 찍어보기로 했다</p>
<ul>
<li>dtype = object일 때,
<img src="https://velog.velcdn.com/images/hong7_/post/9649d7b1-c4d2-497c-9382-07d93c67543d/image.png" alt=""></li>
</ul>
<ul>
<li>dtype = user_numpy_dtype (structured numpy array)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/99f2f7cd-d76c-45df-9a6c-f1c8a4bc17c5/image.png" alt=""></p>
<p>같은 형태의 list를 어떻게 numpy array로 만드는지에 따라 item의 시작 주소값의 차이가 달라졌다.</p>
<p>여기서 다음과 같이 data안에 특정 list에 값을 추가하여 여전히 주소값이 바뀌지 않는지 확이해봤다</p>
<p>바뀌지 않는다면 주소값을 가지고 있는 것이 맞고, 바뀐다면 ctype의 data의 시작 주소를 가리키는 것(단일 포인터)이 맞기 때문이다</p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/24bb20f3-2d69-4590-a7bb-f238aa1b5f3e/image.png" alt=""></p>
<ul>
<li>dtype = object일 때,
<img src="https://velog.velcdn.com/images/hong7_/post/43a8799f-e08e-49a7-b0ae-961475730559/image.png" alt=""></li>
</ul>
<ul>
<li>dtype = user_numpy_dtype (structured numpy array)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/3281d7d7-0464-442f-a3f7-f8da52e77702/image.png" alt=""></p>
<p>dtpye object의 경우 주소값의 차가 바뀌지 않았지만, structured array의 경우 주소값의 차가 변경되었다. 따라서 structured array로 만들면 cdtype의 data를 가리키는 형태(단일 포인터)임을 알 수 있다.</p>
<p>이를 또 알 수 있는 방법이 연산에 대해서 dtype=object는 파이썬의 연산이 동작하고 structured array는 ctype의 연산이 수행됨을 보는 것이다</p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/fd35d762-0f8e-4c91-9b49-a4912aacbb4d/image.png" alt=""></p>
<p>dtype=object에 대해서 * 연산이 반복자 연산으로, python list에 대해서 적용되는 것을 알 수 있다.</p>
<p><img src="blob:https://velog.io/9899eae7-9b77-49e5-999d-6c2ae7b99ddd" alt="업로드중.."></p>
<p>반면 structured array의 경우 * 연산이 ctype의 연산이 적용되어 vectorize연산이 이뤄짐을 알 수 있다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python list and Numpy array
]]></title>
            <link>https://velog.io/@hong7_/Python-list-and-Numpyarray</link>
            <guid>https://velog.io/@hong7_/Python-list-and-Numpyarray</guid>
            <pubDate>Mon, 07 Aug 2023 11:11:36 GMT</pubDate>
            <description><![CDATA[<p>현재 진행중인 프로젝트에서 많은 상품의 정보를 한 번에 받아놓고 후에 특정 feature에 따른 조건 검사를 할 때 numpy를 사용한다. 이 때 numpy array를 사용한 이유는 numpy가 python list보다 빠른 속도와 메모리 효율을 가지며, vectorize가 가능하기 때문이다.</p>
<h3 id="list-와-array">List 와 Array</h3>
<p>list와 array 모두 같은 특성을 갖는 데이터가 순서를 가지고 구성되어 있는 선형 구조이지만, 메모리 상에 어떻게 저장되어 있는지에 따른 많은 차이점을 지닌다
list는 메모리 상에서 비연속적으로 저장(연결 리스트)되며, 때문에 메모리 상의 빈 공간을 효율적으로 사용할 수 있다. 연결 리스트이기 때문에 데이터의 크기가 가변적일 수 있다. 하지만 데이터 접근 관점에서 O(N)의 시간이 들어간다.
array는 메모리 상에서 연속적으로 저장되며, 따라서 같은 크기의 데이터로 구성이 되어야 한다(접근을 위해서). 때문에 크기가 정적이다. 하지만 데이터 접근이 O(1)의 복잡도를 갖는다(메모리 시작 주소 + 데이터 크기 * 인덱스)
array의 경우 물리적으로 연속적으로 저장되어 있기 때문에 cache hit rate 또한 높다(Principle of Locality).</p>
<h3 id="python-list">Python list</h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/967f3040-9b4e-426f-a467-4e3b78019502/image.png" alt="">
python list를 구조화 하면 다음과 같은 형태이다. python은 object class를 상속받은 객체들로 이뤄져있다
여기서 ob_item을 통해서 python list가 item을 어떻게 관리하는지 볼 수 있다. ob_item은 각각의 item 객체의 주소를 가지고 있고 이 주소를 통해 실제 item 객체에 접근해야하는 2중 포인터 구조를 가지고 있다.
결국 마지막에 가서 존재하는 것이 Pyobject이기 때문에 연산에 대해서도 python 연산을 따른다. Pyobject type의 연산은 중간중간 type check에 따른 다수의 except처리가 존재하여 ctype연산보다 긴 시간이 걸리는 것은 당연하다.</p>
<h3 id="numpy-array">Numpy Array</h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d562db4a-8773-44a7-99ff-120bef896b87/image.png" alt="">
Numpy array의 구조는 다음과 같다. python list와 가장 다른 점은 데이터가 2중 포인터 형태가 아닌, 단일 포인터로 관리 된다는 것이다. 또한 데이터가 저장되어있는 형태가 Pyobejct가 아닌 메모리에 직접적으로 값이 저장되어 있는 것을 알 수 있다(ctype data가 저장) 따라서 buff의 시작 주소를 알면, 인덱스를 통해서 빠르게 접근할 수 있으며, cache hit rate도 높을 것 같다.</p>
<h3 id="numpy-array가-python-list-보다-빠를까">numpy array가 python list 보다 빠를까?</h3>
<p>위에 두 자료구조를 비교했을 때, numpy array가 단일 포인터로 접근이 가능하며 메모리 상의 연속적으로 존재하여 cache hit rate가 더 높아 더 빠르다고 생각한다. 또한 메모리에 연속적으로 존재하면 SIMD가 가능해진다(SIMD는 데이터가 정렬된 상태에서 가능하다 
<img src="https://velog.velcdn.com/images/hong7_/post/7f440386-e181-47eb-b951-7a3a91f83a31/image.png" alt=""></p>
<p>numpy arrary의 vectorize 연산이 이 SIMD를 통해서 이뤄진다.
결과적으로 numpy array가 같은 타입의 데이터로 이뤄져 있기 때문에 메모리에 연속적으로 저장이 되어 접근이 빠르고, cache hit rate가 높으며 결과적으로 SIMD가 가능한다.</p>
<p>그렇다면 다양한 데이터 타입으로 구성되어있는 데이터는 numpy array로 다룰 수 없을까? (다음 포스팅에서..)</p>
<p><a href="https://devocean.sk.com/blog/techBoardDetail.do?ID=163631&amp;searchData=Programming+Language&amp;page=&amp;subIndex=%EC%B5%9C%EC%8B%A0+%EA%B8%B0%EC%88%A0+%EB%B8%94%EB%A1%9C%EA%B7%B8&amp;idList=%5B163644%2C+163642%2C+163645%2C+163635%2C+163639%2C+163636%2C+163638%2C+163626%2C+163624%2C+163628%2C+163607%2C+163631%2C+163605%2C+163619%2C+163608%2C+163610%2C+163606%2C+163593%2C+163599%2C+163590%2C+163557%2C+163575%2C+163580%2C+163578%2C+163545%2C+163554%2C+163569%2C+163566%2C+163561%2C+163547%2C+163311%2C+163553%2C+163529%2C+163544%2C+163532%2C+163537%2C+163530%2C+163528%2C+163522%2C+163523%2C+163425%2C+163436%2C+163491%2C+163512%2C+163477%2C+163488%2C+163508%2C+163499%2C+163492%2C+163476%2C+163490%2C+163494%2C+163482%2C+163483%2C+163484%2C+163480%2C+163481%2C+163479%2C+163470%2C+163478%2C+163471%2C+163475%2C+163465%2C+163423%2C+163421%2C+163466%2C+163432%2C+163458%2C+163434%2C+163419%2C+163448%2C+163431%2C+163455%2C+163435%2C+163440%2C+163454%2C+163447%2C+163433%2C+163430%2C+163415%2C+163429%2C+163424%2C+163420%2C+163417%2C+163409%2C+163347%2C+163408%2C+163406%2C+163380%2C+163392%2C+163346%2C+163399%2C+163400%2C+163379%2C+163390%2C+163354%2C+163381%2C+163345%2C+163360%2C+163365%2C+163338%2C+163344%2C+163375%2C+163364%2C+163301%2C+163340%2C+163103%2C+163359%2C+163363%2C+163296%2C+163356%2C+163350%2C+163349%2C+163295%2C+163343%2C+163327%2C+163335%2C+163334%2C+163336%2C+163330%2C+163331%2C+163326%2C+163328%2C+163324%2C+163325%2C+163321%2C+163294%2C+163211%2C+163309%2C+163307%2C+163266%2C+163290%2C+163282%2C+163268%2C+163278%2C+163280%2C+163281%2C+163277%2C+163279%2C+163263%2C+163262%2C+163251%2C+163249%2C+163243%2C+163244%2C+163245%2C+163238%2C+163237%2C+163223%2C+163232%2C+163233%2C+163230%2C+163212%2C+163214%2C+163228%2C+163229%2C+163217%2C+163219%2C+163221%2C+163222%2C+163224%2C+163225%2C+163213%2C+163197%2C+163205%2C+163196%2C+163191%2C+163192%2C+163189%2C+163185%2C+163179%2C+163173%2C+163168%2C+163170%2C+163167%2C+163152%2C+163138%2C+163127%2C+163125%2C+163117%2C+163118%2C+163119%2C+163114%2C+163116%2C+163107%2C+163079%2C+163054%2C+163055%2C+163029%2C+163008%2C+162986%2C+162994%2C+162976%2C+162968%2C+162962%2C+162915%2C+162907%2C+162904%2C+162750%2C+162735%2C+162714%2C+162713%2C+162700%2C+162692%2C+162576%2C+162534%2C+161250%2C+161147%2C+160842%2C+160835%2C+160620%2C+160623%2C+160298%2C+160038%2C+160012%2C+159980%2C+159948%2C+159409%2C+159414%2C+159334%2C+159337%2C+159339%2C+159311%2C+159274%2C+159229%2C+159230%2C+159104%2C+159075%2C+158966%2C+158914%2C+158882%2C+158883%2C+158803%2C+158774%2C+158688%2C+158674%2C+158657%2C+158649%2C+158639%2C+158512%2C+158484%2C+158482%2C+158466%2C+158428%2C+157805%2C+55053%2C+54957%2C+54840%2C+54295%2C+54041%2C+53660%2C+53290%2C+40435%2C+193%5D">참고링크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스타트업에서 나홀로 식단 추천 시스템 만들기(2)]]></title>
            <link>https://velog.io/@hong7_/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%97%90%EC%84%9C-%EB%82%98%ED%99%80%EB%A1%9C-%EC%8B%9D%EB%8B%A8-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B02</link>
            <guid>https://velog.io/@hong7_/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%97%90%EC%84%9C-%EB%82%98%ED%99%80%EB%A1%9C-%EC%8B%9D%EB%8B%A8-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B02</guid>
            <pubDate>Thu, 27 Jul 2023 13:38:54 GMT</pubDate>
            <description><![CDATA[<h3 id="rule-기반에서-벗어날-수-있을까">Rule 기반에서 벗어날 수 있을까?</h3>
<hr>
<p>식단이라는 지켜야하는 규칙이 상당히 많은 컨텐츠를 정해진 규칙없이 만들 수 있을까? 라는 고민에 도달하게 되었다. 식단을 구성하는 음식들을 예측할 수 있다면 유저의 선호를 음식 단위로 관리하여 위에 있던 문제들을 상당 부분 처리할 수 있을 것이라고 생각하였다. 음식을 예측하는 모델을 만들기 위해서는 딥러닝을 알 필요가 있었고, 필요한 부분을 빠르게 학습 하기로 하였다. 식단의 경우 <strong>맥락(context)이 존재하는 sequence 데이터라고 생각했다</strong>(앞에 나온 음식이 뒤에 음식에 영향을 미치기 때문에)<strong>.</strong> 그래서 자연어와 관련된 부분을 빠르게 학습하며 병렬적으로 여러 가지 시도를 해보게 되었다. <strong>이제까지 유저에게 추천해주었던 식단을 데이터로 하여, 이를 학습하여 식단을 만들 수 있는 모델</strong>을 만들고자 하였다. 이 때, 기존에 추후에 필요할지 몰라서 식단 추천 시에 그 세부 내용을 로그 형태로 기록하는 DB 스키마를 정의했던게 도움이 되었다.</p>
<h3 id="딥러닝을-어떻게-활용할-수-있을까">딥러닝을 어떻게 활용할 수 있을까?</h3>
<hr>
<p>처음에는 식품 자체를 예측하려고 했으나, 분류 문제로 생각했을 때 식품의 입점 상황이 빠르게 변화하여 과거에 있던 식품이 지금은 없을 수 있고 새롭게 추가된 식품들을 예측하려면 모델을 식품을 추가할 때마다 새롭게 학습하고 output의 크기가 바뀌어야한다. 하지만 해당 과정이 빠르게 이뤄질 수 없을 것이라고 판단하여 음식의 속성 중에 하나인 카테고리를 예측하면 어떨까 생각하게 되었다. 카테고리는 크게 바뀔 일이 없고, 새롭게 추가되는 음식들도 기존에 있는 카테고리에 포함되기 때문에 음식을 실시간으로 추가하더라도 모델을 새롭운 음식을 문제 없이 추천할 수 있다. 따라서 구현 난이도와 속도를 고려하였을 때 카테고리 예측을 먼저 시도해보기로 했다. </p>
<p>다양한 시도가 있었지만 최종적으로는, LSTM과 Word2Vec를 활용하여 모델을 구현하게 되었다. 맥락이 있다는 점에서 앞에 내용이 뒤에도 영향을 미쳐야하기 때문에 LSTM을 활용하였다. 또한 카테고리를 임베딩할 때 카테고리간의 유사도 또한 판단할 수 있도록 Word2Vec를 사용하여 카테고리 임베딩을 진행하였다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/5a4ab1a4-7e21-4d74-8c94-dbbea1d029b7/image.png" alt=""></p>
<p>input으로는 다양한 유저의 정보(신체 정보와 관련된 데이터 + 선호/비선호에 대한 데이터)와, 구매 데이터에서 얻은 데이터, 끼니 별 음식 수(이 또한 Fully connected 를 이용하여 예측하였다)등을 이용하였고, output으로는 1주일 식단의 카테고리였다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/46fe97c4-5d79-4bb0-a890-300e84048d1b/image.png" alt=""></p>
<p>카테고리 예측 가정을 거친 뒤에 유저가 비선호하는 카테고리에 대해서 필터 과정을 거친 뒤에 카테고리가 부족하거나 없어지는 경우에는 <strong>Word2Vec를 이용한 embedding을 기반으로 유사한 카테고리로 확장</strong>하여 추천하도록 하였다. 유사도를 기반으로 한 확장을 통해 <strong>다양성도 확보</strong>할 수 있었다. 이 후 <strong>카테고리를 기반으로 음식들을 추출하여 실시간으로 조합하고 몇 가지 조건 검사를 한 뒤 최종 추천</strong>을 하는 시스템을 구축하였다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/bd76f6ce-1b9c-4ea3-8b32-e4a7474e100a/image.png" alt=""></p>
<p>측 이 과정을 통해서 식단의 각 위치 별로 가능한 음식들의 후보군(candidates)를 빠르게 추출할 수 있는 것이다. 이 때 당시에는 구매 예측에 대한 모델을 넣기 보단 우선은 해당 후보군 끼리의 모든 조합 중 조건을 만족하는 끼니들을 일정한 규칙에 따라 조합하여 식단을 생성하였다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/28473d8e-c417-41df-8d14-d397dae12999/image.png" alt=""></p>
<p>모델의 예측 성능은 대략 60%정도였지만, 정확하게 예측하기 보다 다양하게 예측하며 사람이 보기에 자연스러운지가 더 중요하였다. 따라서 <strong>A/B 테스트를 진행</strong>하였다. A/B테스트 진행 결과 결제 전환율에서 약 6%의 수치로 기존 룰과 비슷한 수준의 결과가 나왔다. 물론 딥러닝 자체가 구매 데이터를 활용한 것이 아니라, 추천 식단을 기반으로 학습하였기 때문에 결제 전황에서 크게 상승하지는 않았지만, 결제 전환이 하락하거나 식단이 이상하다는 CS가 들어오지 않았기 때문에 성공적으로 딥러닝을 이용한 시스템으로 옮겼다고 판단하였다. </p>
<p>딥러닝 방식으로 옮기면서 점수제 방식에서는 필터로 처리하지 못했던 부분들을 필터 처리로 바꿀 수 있었다. 따라서 유저가 싫어한다고 표현한 요소에 대해서는 추천에서 볼 일이 사라지게 된다. 유저 Interview 결과 유저는 비선호 상품이 노출되었을시 서비스 전반적인 호감도가 크게 하락함을 알게되었다. 따라서 비선호를 완전히 제거할 수 있게 된 것은 향 후 결제 전환율에 긍정적인 영향을 미칠 것으로 기대한다. 또한 추가 입점이 있을 시에 별도로 끼니를 만들어 DB에 입력한다거나, 매 코호트마다 DB에 추천 시스템과 관련하여 미리 데이터를 올릴 필요성이 사라졌다. 이로 인해 배포 전 필요 작업 누락으로 인한 버그들이 사라졌다. </p>
<p>성과 추가) 딥러닝 기반 카테고리 예측에 따른 후보군 생성으로 인해 유저에게 추천해줄 수 있는 조합과 상품의 다양성이 증가하였고 2회차 재구매율 50%를 달성할 수 있었다. 비선호 상품 노출로 인한 CS가 90% 감소하였다. </p>
<h3 id="어떻게-하면-결제-전환과-리텐션을-올릴-수-있을까">어떻게 하면 결제 전환과 리텐션을 올릴 수 있을까?</h3>
<hr>
<p>여기 까지는 ‘어떻게 하면 식단을 만들 수 있을까?’에 초점이 맞춰져있었다. 물론 유저에게 더 맞는 식단을 만들기 위해서 explicit/implicit feedback을 이용하였지만 구매 확률이 높은 음식을 예측한 것은 아니었다. 그렇다면 비지니스 측면에서 어떻게 하면 유저들의 구매 전환율이나 리텐션을 증가시킬 수 있을까? </p>
<ul>
<li>여전히 필터 요소 때문에 식품 입점 상황에 따라 추천 실패가 발생하여 유저가 추천도 받아보지 못하고 이탈하는 경우가 발생한다.</li>
<li>조건을 만족하면서도, 유저가 구매할 만한 상품들을 위주로 추천한다</li>
</ul>
<p>1번에 대해서는 이제까지 추천 실패로 받아보지 못했던 유저들이 식단을 받아보고 1명이라도 결제로 전환이 된다면, 결제 전환에 + 방향으로 영향을 미치게 된다. </p>
<p>2번의 경우에는 최근 wide &amp; deep learning for recommendation논문을 읽고 모델을 구현한 적이 있었는데 이를 활용하면 구매 예측을 할 수 있을 것이라고 생각하였다 </p>
<h3 id="유저의-특성에-따라-추천에-사용하는-필터를-유동적으로-한다">유저의 특성에 따라 추천에 사용하는 필터를 유동적으로 한다</h3>
<hr>
<p>유저에게 추천 컨텐츠(식단)을 제공할 수 없는 경우는 보유하는 음식에 비해서 유저가 비선호하는 요소에 속하는 음식의 비율이 월등히 높아 식단을 구성할 수 없는 경우 때문이다. 이를 해결하기 위해서 식단 생성을 시도해보고 실패시에 차례로 많은 영향을 미치는 필터링 요소를 소거하여 추천을 하는 방식을 채택하였다. 이를 위해서 API요청 시에 property를 전달하여 필터 요소를 조절하는 방식을 사용하였다. </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/f49bebbd-c80e-464a-864e-838242a792c6/image.png" alt=""></p>
<p>영향을 많이 미치는 요소들을 측정하기 위해서, 표본 집단을 추출하고 동일한 표본 집단에 대해서 필터 요소를 바꿔보면서 얼마나 추천에 영향을 미치는지를 측정하였다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/65cb2d30-7e06-4134-97c7-ab0f82665a1e/image.png" alt=""></p>
<p>예를 들어 알러지요소만 검사할 경우, 영양소 요소의 경우 제공된 식단의 평균적으로 93%의 끼니가 영양소 기준을 지키지 못하여 분산은 5.9가 된다는 것이다. 따라서 해당 실험 결과를 통해서 알러지 외의 다른 요소 중에는 영양소, 비선호 카테고리가 가장 많은 영향을 끼친다는 것을 알게되었습니다. 이에 따라 시도 실패에 따라서 알러지/영양소/비선호 카테고리 만 지키는 생성을 시도하고, 이후에 알러지를 제외한 요소들을 하나씩 소거하며 생성하도록 하였습니다. 이를 배포하여, 전체 추천 실패가 0명이 되었다. 또한 샐패 대응에 따른 생성에 대해서도 결제 전환율이 10%로 1000명의 유저가 기존에 추천도 못받고 이탈 하였다면 이제는 그 중 100명을 결제 유저로 전환하게 된 것이다. 만약 전체 유저에 대해서 기존 실패 확률이 10%라면, 이에 대한 10% 이므로 전체 유저의 1%가 된다. 이를 통해 결제 전환에 1%의 상승을 기여하게 되었다. </p>
<h3 id="구매-확률이-높은-음식들을-위주로-식단-구성하기">구매 확률이 높은 음식들을 위주로 식단 구성하기</h3>
<hr>
<p>식단을 생성하는 과정에서, 식품의 후보군을 만든 후에 이 후보군에 대해서 wide &amp; deep model을 이용하여 구매 확률에 대해 예측한 뒷 랭킹화시켜 높은 랭크에 있는 음식을 위주로 추천하는 방식을 생각하게 되었다. 물론 구매 확률이 높더라도, 영양 조건을 못 지키거나 기타 다른 필터 요소를 지키지 못할 때는 추천 하지 못할 수 있다<strong>(고도화 시키면서 느꼈던 것은 유저가 구매하고 싶어하는 상품과 건강한 요소를 지키기 위해서는 유저 선호만을 따라갈 수 없다는 것이 상당히 챌린지 하다는 것이다.)</strong> </p>
<p>wide &amp; deep 모델을 다음과 같이 구현하였다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/bab95a73-e4a7-4e18-8474-916c14b116f5/image.png" alt=""></p>
<p>기존 결제 데이터를 활용하여, 간단하게 모델을 구현한 뒤 학습한 결과 약 70%의 acc이 나왔다. 이를 좀 더 고도화 하여서 프러덕트에 적용시킨다면 유저의 객단가, 결제 전환율, 재결제에 긍정적인 영향을 미칠 수 있다고 생각한다. 현재는 해당 프로젝트를 진행중에 있다. </p>
<p>성과 추가) 해당 구매 예측 모델의 도입으로 결제 전환율에서 25%의 개선이 있었다 
결과적으로 만들어진 시스템을 도식화하면 다음과 같다
<img src="https://velog.velcdn.com/images/hong7_/post/78c970a0-f873-433a-a42c-09950341c2aa/image.png" alt=""></p>
<h3 id="그-간의-경험">그 간의 경험</h3>
<hr>
<p>‘나홀로’ 라고 표기한 이유는 처음 아이템을 시작할 때 개발팀의 인원이 나를 포함여 3명이었고, 각각 자신의 역할을 맡아 구현을 시작하였기 때문이다. 이 후 개발팀의 인원이 충원 되긴 했지만, 내가 하고있는 데이터 업무에는 인원이 충원되고 있지 않아 혼자 시스템을 고도화 해야하는 <del>외로운 경험</del>을 하고있다. 하지만 해당 프로젝트를 진행하면서 오히려 전문 영양사님이나 기획팀과의 교류가 상당히 많았던 것 같다. 그 과정에서 더 좋은 설득이나 회의 진행을 위해서 미리 데이터를 시각화하여 준비하면 좋다는 것, 이야기 할 점들을 미리 대략적으로라도 정리하고 의사소통에 임하는 것 등등을 배우게 된 것 같다. 물론 개발적으로도 고도화를 진행하면서 전반적인 코드 설계, CS적인 지식, 전에는 몰랐던 DL에 대한 부분들을 빠르게 학습하고 적용하는 경험도 해볼 수 있게 되었다. 여러 가지 공부를 하면서 적용해보는 것도 좋았지만, 역시나 나보다 경험이 많은 분과 함께 프로젝트를 진행하며 어깨 너머로 배우고 시너지를 낼 수 있었다면 더 좋지 않을까하는 아쉬움은 남아있다! 그래도 그 간의 과정들을 정리하고보니 처음과 비교 하였을 때 성장했음을 느낄 수 있어서 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스타트업에서 나홀로 식단 추천 시스템 만들기(1)]]></title>
            <link>https://velog.io/@hong7_/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%97%90%EC%84%9C-%EB%82%98%ED%99%80%EB%A1%9C-%EC%8B%9D%EB%8B%A8-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B01</link>
            <guid>https://velog.io/@hong7_/%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%97%90%EC%84%9C-%EB%82%98%ED%99%80%EB%A1%9C-%EC%8B%9D%EB%8B%A8-%EC%B6%94%EC%B2%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B01</guid>
            <pubDate>Thu, 27 Jul 2023 13:35:41 GMT</pubDate>
            <description><![CDATA[<p>처음 추천 시스템을 만들기로 할 때는 추천 시스템에 대해서 전혀 무지했다. 새롭게 시작하는 프러덕트에서 핵심 기능으로 입점된 냉동/냉장/신선 식품으로 구성된 1주일 식단을 생성/추천을 기획하게 되었고 아무 것도 모를 때 용감하다고 ‘제가 한번 해보겠습니다!’를 외쳤다. 현재 진행형인 식단 추천 시스템에 대해 자세한 구현 방식 보다는 지난 시간 동안 어떤 문제들을 어떤 질문들과 솔루션으로 해결하고 고도화 했는지 공유하고자 한다.</p>
<h3 id="추천-해야하는-컨텐츠의-속성이-뭘까">추천 해야하는 컨텐츠의 속성이 뭘까?</h3>
<hr>
<p>식단을 구성하기 위해서 식단에 대해서 전문 영양사님과 긴밀하게 소통하며 여러 가지를 정의하고자 했다. 그 중 추천해줘야 하는 컨텐츠 자체에 집중해보면 다음과 같은 특징이 있다. </p>
<p>1주일 식단을 구성하는 것은 아침/점심/저녁/간식에 해당 하는 끼니들이고, 끼니를 구성하는 것은 최대 3개의 음식이다. 우리가 가지고 있는 아이템은 ‘식품’이다. 이를 여러 개 조합하여 ‘끼니’를 만들고, 이 끼니도 여러 방법으로 조합하고 나서야 최종 형태인 식단이 된다. </p>
<p>추천을 처음 생각했을 때 넷플릭스, 유튜브, 기타 커머스들이 떠올랐는데, 영상이나 상품 자체가 큐레이션 형태로 추천 되는 것이 아닌 아이템을 수십 개 조합하여 하나의 추천 컨텐츠(식단)를 만들어야 하는게 달랐다. 또한 후에 고도화를 진행하면서 각각의 식품이 끼니를 구성할 때 ‘<strong>자연스러워야하고</strong>’, 1주일 식단안에는 <strong>일종의 맥락(context)</strong>이 존재한다는 것을 인지하였다. 또한 건강 식단이므로, 영양소 범위/알러지 등 <strong>특정 조건은 아주 엄격</strong>하게 지켜야한다는 점이 있었다. </p>
<h3 id="우선은-최종-제공-형태를-만들어-db에-저장하자">우선은 최종 제공 형태를 만들어 DB에 저장하자</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/1f3b4094-cbe5-4014-ade7-1f6d833a7259/image.png" alt=""></p>
<p>첫 시도에서 다양한 시행 착오가 있었다. 음식을 특징을 고려하여 총 3개의 category로 나눠 모든 음식을 조합하여 조건 검사를 통해 필터링한 ‘안전한 끼니’를 만들어 랜덤하게 조합하여 식단을 만드는 방식은 모든 음식을 조합하는 것이 너무 오랜 시간이 걸렸다. 따라서 카테고리 별로 음식을 랜덤하게 추출하고 조합된 끼니가 조건을 만족하는지 검사하여, 원하는 숫자만큼의 끼니가 만들어질 때 까지 반복하는 것을 선택하였다. 이를 효율적으로 하기 위해서 유전 알고리즘을 참고하여 낮은 점수로 조건을 만족한 끼니에 대해서 별도의 처리를 하여 진행하였다. 빠르게는 3초 정도의 식단이 나오기도 했지만, 아무리 반복해도 식단이 나올지 불명확한 경우도 다수 있었다. </p>
<p>처음부터 완벽한 시스템을 만들기보다 <strong>“일단은 빠르게 출시해보고 반응을 보자”가</strong> 목적이었기 때문에 알고리즘을 자동으로 돌게하는 스크립트를 작성하여 배포 전에 미리 DB에 최종 형태의 식단을 저장하여 제공하기로 하였다. 이렇게 진행하여 API요청 시에 많은 컴퓨터 리소스가 들지 않고 식단을 제공할 수 있었고, 충분히 반응을 볼 수 있었다. </p>
<p>테스트를 통해 결제로 이어진 경우도 있었지만, 전체의 1%~2%이내로 결제 전환이 일어날 뿐이었다. 하지만 테스트를 통해서 다양한 피드백을 수집할 수 있었다. </p>
<ul>
<li>자신의 선호/비선호를 반영할 수는 없는지</li>
<li><strong>끼니의 구성이 부자연스럽다</strong><ul>
<li>유저 테스트 당시 부자연스러움을 느끼는 부분은 개인마다 달랐다</li>
<li>유저들이 부자연스러움을 느끼는 포인트는 식단/끼니 내의 맥락(ex. 아침에 이런 음식이 추천이 되는 것이 이상하다, 특정 끼니를 구성하는 음식들이 어울리지 않는 것 같다)와 같은 것이었다.</li>
</ul>
</li>
<li>식단 내에 반복이 존재한다 / 다양성이 부족하다</li>
</ul>
<p>이를 통해 추천에 중요한 요소가 개인의 선호 반영은 물론이고, 컨텐츠 자체의 맥락, 다양성도 중요하다는 것을 알게되었다. </p>
<h3 id="유저의-선호를-알-수-있는-장치를-심자">유저의 선호를 알 수 있는 장치를 심자</h3>
<hr>
<p>유저의 선호를 알기 위해서 가능한 장치를 생각해보게 되었다. 기존 유저 별 권장 섭취 칼로리 계산을 위한 정보(신장, 운동량 등등)만 수집하던 온보딩(Onboarding)단계에서 유저의 선호와 비선호를 묻는 단계를 다양한 방식으로 추가하여 실험하였다. 또한 음식을 주문하고 먹어본 뒤에 해당 음식이 좋았는지 안좋았는지 피드백을 줄 수 있는 thumbs up/down 및 bookmark과 같은 장치들을 추가하였다. 지금 생각해보면 이런 explicit feedback을 받은 것이 유저 수가 적었던 점과 빠르게 유저의 선호를 반영 해야하는 상황에 좋은 판단이었던 것 같다. </p>
<h3 id="user-feedback과-numpy-array를-활용하여-rating-system-만들기">User Feedback과 Numpy array를 활용하여 Rating System 만들기</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/0ad3945d-2bd5-4873-a37b-89c8f330a2a0/image.png" alt=""></p>
<p>본격적인 작업전에 ‘<strong>부자연스러움</strong>’을 해소하기 위해서 많은 유저 테스트를 거쳐, 자연스러운 ‘끼니’를 만들 수 있는 규칙을 정하게 되었다. 이를 기반으로 식품을 더 고도화된 category로 나누었다.(일반적인 식품 카테고리와 별개로 자연스러움을 위해 정의한 카테고리였다) 해당 카테고리를 기반으로 다양한 조건을 검사하는 수 십만 개의 끼니를 만들 수 있는 시스템을 먼저 만들었다. 물론 DB 저장 공간의 이유와 너무 많은 데이터를 로드할 수 없기 때문에 최종적으로는 균현을 고려하여 3만개의 끼니만 DB에 저장하였다 (이 작업을 배포때마다 해줘야 했다 - 식품이 추가될 때마다 새로운 조합의 끼니를 만들어주기 위해서) </p>
<p>Django를 사용하고 있었기 때문에 처음에는 만들어지 끼니에 대해서 ORM을 통해 요청 시에 유저 데이터를 활용하여 filter하는 방식을 선택했으나, 코드가 너무 복잡해져 새로운 조건을 추가하기가 어려웠고(유동적이지 못했고) 필터의 순서도 결과에 영향을 미치게 된다. 또한 속도도 느리고 추천이 불가능한 경우도 다수 발생하는 문제점은 그대로 남아있었다. 좋은 솔루션이 아니라고 생각하여 어떻게 다음 3가지를 해결할 수 있을지 고민하다가 Numpy array를 활용한 Rating system을 만들게 되었다 </p>
<ul>
<li>컴퓨터 리소스를 크게 들이지 않고 수 만개의 끼니들에 유저 선호 및 다양성을 반영한 점수를 매길 수 있을까?</li>
<li>추후에 또 다른 피드백이 들어왔을 때 유연하게 대응할 수 있는 방법이 무엇일까?</li>
<li>식단을 만들 수 없어 추천에 실패하는 경우를 최소화 할 수 있을까?<ul>
<li>유저의 알러지가 보유한 음식에 대부분 포함되어 추천할 수 없는 경우를 제외하고, 필터 방식으로 진행하지 않으면서 유저의 선호를 제일 가깝게 맞출 수 있는 방법이 무엇일까?</li>
</ul>
</li>
</ul>
<p>먼저 numpy array는 python에는 존재하지 않는 array를 사용할 수 있게 해준다. python은 모든 것이 객체인 만큼 각각의 객체가 메모리 공간에 따로 존재하고 해당 주소값을 통해 관리하는 list만 존재하였다. 하지만 numpy는 C의 array와 같이 실제 메모리 공간에 연속되게 저장해주기 때문에 병렬처리가 가능하였다. 이 장점을 이용하여 컴퓨터 리소스를 크게 들이지 않고 여러 조건을 검사할 수 있다. 독립적인 조건을 검사하는 여러 개의 층(layer)를 만들고 각 아이템(끼니)에 매겨진 점수를 모두 합하면 아이템 별 최종 점수가 나오기 때문에 <strong>순위를 매길 수 있게 된다.</strong> 또한 새로운 조건이 추가되면 그 떄마다 layer를 추가하면 되는 <strong>유동적인 구조</strong>를 만들 수 있었다.</p>
<p>조건 알러지를 제외하고 모든 것을 점수화하였다(알러지는 어떤 경우에도 추천되면 안된다). 유저의 explicit feedback을 기반으로한 조건도 있지만, implicit feedback을 기반으로한 조건도 추가되었다. 조건 간의 우선순위가 있기 때문에, layer에 따른 가중치를 정의하였고, 최종적으로는 가중치 행렬과 점수 행렬의 곱을 통해서 최종 점수를 계산하는 시스템을 만들게 되었다 </p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/8aca4dcb-4817-4f84-9d07-d988de1eab9e/image.png" alt=""></p>
<p>해당 방식은 좋은 반응이 있었다. 코호트 당 결제 전환이 5%<del>10%까지 올랐다. API 요청시에 rating system자체는 1초가 걸리지 않았고, 뽑힌 식단을 후가공하여 DB에 최종적으로 저장까지 하는 전체 과정은 2초</del>3초가 정도였다. </p>
<p>하지만 조건이 늘어남에 따라 layer 가중치를 다루는 것이 쉽지 않았다. 기존에는 기존 유저 데이터를 기반으로 표본 집단을 추출하여 건전성 검사 코드를 만들어 결과를 추출한 뒤 layer 가중치를 조절하는 방식으로 진행하였다. 하지만 조건이 많아짐에 따라 가중치 값을 관리하는 것이 어려워졌다. 또한 정해준 규칙(Rule)로 의해 만들어진 끼니들로 인해 다양성이 부족하다는 user feedback이 있었다. 마지막으로 유저들은 비선호에 대해서는 확실한 필터링을 원했는데, 끼니 단위로 점수를 매기다 보니 끼니 중 점수가 월등히 높은 음식이 포함되어있을 때 비선호하는 상품이 같이 추천되어 좋지 않다는 feedback도 수집할 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Wide and Deep Learning for Recommender Sys 구현 ]]></title>
            <link>https://velog.io/@hong7_/Wide-and-Deep-Learning-for-Recommender-Sys-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@hong7_/Wide-and-Deep-Learning-for-Recommender-Sys-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 19 Jul 2023 09:54:24 GMT</pubDate>
            <description><![CDATA[<ul>
<li>wide &amp; deep 논문을 읽고, 간략하게 모델 구현</li>
<li>논문에 언급되었던 wide model에 input으로 주기 위한 cross-product과정은 다음을 참고하였습니다
<a href="https://github.com/jrzaurin/pytorch-widedeep">https://github.com/jrzaurin/pytorch-widedeep</a></li>
<li>keras를 이용한 deep model의 구현과 최종 wide deep model의 구현에 있어서는 다음을 참고하였습니다 
<a href="https://lsjsj92.tistory.com/597">https://lsjsj92.tistory.com/597</a></li>
<li>wide 와 deep model을 concat하여 sigmoid function에 통과시켜 최종적인 확률 값을 얻고자 하였습니다</li>
</ul>
<h3 id="데이터-로드">데이터 로드</h3>
<ul>
<li>데이터는 kmrd 데이터 셋을 사용하였습니다.
(movies, castings, countries, genres)</li>
</ul>
<pre><code class="language-python">movies_df = pd.read_csv(&#39;../data/kmrd/kmr_dataset/datafile/kmrd-small/movies.txt&#39;, sep=&#39;\t&#39;, encoding=&#39;utf-8&#39;)
movies_df = movies_df.set_index(&#39;movie&#39;)

castings_df = pd.read_csv(&#39;../data/kmrd/kmr_dataset/datafile/kmrd-small/castings.csv&#39;, encoding=&#39;utf-8&#39;)
countries_df = pd.read_csv(&#39;../data/kmrd/kmr_dataset/datafile/kmrd-small/countries.csv&#39;, encoding=&#39;utf-8&#39;)
genres_df = pd.read_csv(&#39;../data/kmrd/kmr_dataset/datafile/kmrd-small/genres.csv&#39;, encoding=&#39;utf-8&#39;)

# Get genre information
genres = [(list(set(x[&#39;movie&#39;].values))[0], &#39;/&#39;.join(x[&#39;genre&#39;].values)) for index, x in genres_df.groupby(&#39;movie&#39;)]
combined_genres_df = pd.DataFrame(data=genres, columns=[&#39;movie&#39;, &#39;genres&#39;])
combined_genres_df = combined_genres_df.set_index(&#39;movie&#39;)

# Get castings information
castings = [(list(set(x[&#39;movie&#39;].values))[0], x[&#39;people&#39;].values) for index, x in castings_df.groupby(&#39;movie&#39;)]
combined_castings_df = pd.DataFrame(data=castings, columns=[&#39;movie&#39;,&#39;people&#39;])
combined_castings_df = combined_castings_df.set_index(&#39;movie&#39;)

# Get countries for movie information
countries = [(list(set(x[&#39;movie&#39;].values))[0], &#39;/&#39;.join(x[&#39;country&#39;].values)) for index, x in countries_df.groupby(&#39;movie&#39;)]
combined_countries_df = pd.DataFrame(data=countries, columns=[&#39;movie&#39;, &#39;country&#39;])
combined_countries_df = combined_countries_df.set_index(&#39;movie&#39;)</code></pre>
<ul>
<li><p>각각의 dataframe을 이용하여 영화 정보에 대한 dataframe을 우선 생성하였습니다</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/24d048f7-0bd8-48e8-8529-8a30fdbe8ad8/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>결측치를 가지고 있는 row를 drop하여 최종적으로 999개 영화중에 583개의 영화 데이터를 사용하였습니다</li>
<li>유저가 어떤 영화를 좋아할지를 예측해야하므로, 유저의 rates 정보에 영화 데이터를 붙여주는 방식으로 train dataframe을 준비하였습다</li>
</ul>
<pre><code class="language-python">train_df[&#39;genres&#39;] = train_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;genres&#39;], axis=1)
val_df[&#39;genres&#39;] = val_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;genres&#39;], axis=1)

train_df[&#39;country&#39;] = train_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;country&#39;], axis=1)
val_df[&#39;country&#39;] = val_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;country&#39;], axis=1)

train_df[&#39;grade&#39;] = train_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;grade&#39;], axis=1)
val_df[&#39;grade&#39;] = val_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;grade&#39;], axis=1)

train_df[&#39;year&#39;] = train_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;year&#39;], axis=1)
val_df[&#39;year&#39;] = val_df.apply(lambda x: movies_df.loc[x[&#39;movie&#39;]][&#39;year&#39;], axis=1)</code></pre>
<p><img src="https://velog.velcdn.com/images/hong7_/post/546d6589-ab4d-4439-8055-089f5c567b12/image.png" alt=""></p>
<ul>
<li>label값은 평점이 8점 이상인지 아닌지를 기준으로 생성하였습니다</li>
</ul>
<h3 id="wide-preprocessor">Wide Preprocessor</h3>
<ul>
<li>wide 모델에 넣기전 cross-product를 진행하게 됩니다</li>
<li><a href="https://github.com/jrzaurin/pytorch-widedeep">https://github.com/jrzaurin/pytorch-widedeep</a>의 wide preprocessor를 이용하였습니다</li>
</ul>
<pre><code class="language-python">from typing import List, Tuple

import numpy as np
import pandas as pd

class WidePreprocessor():
    def __init__(self, wide_cols: List[str], crossed_cols: List[Tuple[str, str]] = None):
        self.wide_cols = wide_cols
        self.crossed_cols = crossed_cols

    def fit(self, df: pd.DataFrame) -&gt; &quot;WidePreprocessor&quot;:
        df_wide = self._prepare_wide(df)
        self.wide_crossed_cols = df_wide.columns.tolist()
        glob_feature_list = self._make_global_feature_list(
            df_wide[self.wide_crossed_cols]
        )
        # leave 0 for padding/&quot;unseen&quot; categories
        self.encoding_dict = {v: i + 1 for i, v in enumerate(glob_feature_list)}
        self.wide_dim = len(self.encoding_dict)
        self.inverse_encoding_dict = {k: v for v, k in self.encoding_dict.items()}
        self.inverse_encoding_dict[0] = &quot;unseen&quot;
        return self

    def transform(self, df: pd.DataFrame) -&gt; np.ndarray:
        df_wide = self._prepare_wide(df)
        encoded = np.zeros([len(df_wide), len(self.wide_crossed_cols)])
        for col_i, col in enumerate(self.wide_crossed_cols):
            encoded[:, col_i] = df_wide[col].apply(
                lambda x: self.encoding_dict[col + &quot;_&quot; + str(x)]
                if col + &quot;_&quot; + str(x) in self.encoding_dict
                else 0
            )
        return encoded.astype(&quot;int64&quot;)

    def inverse_transform(self, encoded: np.ndarray) -&gt; pd.DataFrame:
        decoded = pd.DataFrame(encoded, columns=self.wide_crossed_cols)
        decoded = decoded.applymap(lambda x: self.inverse_encoding_dict[x])
        for col in decoded.columns:
            rm_str = &quot;&quot;.join([col, &quot;_&quot;])
            decoded[col] = decoded[col].apply(lambda x: x.replace(rm_str, &quot;&quot;))
        return decoded

    def fit_transform(self, df: pd.DataFrame) -&gt; np.ndarray:
        return self.fit(df).transform(df)

    def _make_global_feature_list(self, df: pd.DataFrame) -&gt; List:
        glob_feature_list = []
        for column in df.columns:
            glob_feature_list += self._make_column_feature_list(df[column])
        return glob_feature_list

    def _make_column_feature_list(self, s: pd.Series) -&gt; List:
        return [s.name + &quot;_&quot; + str(x) for x in s.unique()]

    def _cross_cols(self, df: pd.DataFrame):
        df_cc = df.copy()
        crossed_colnames = []
        for cols in self.crossed_cols:
            for c in cols:
                df_cc[c] = df_cc[c].astype(&quot;str&quot;)
            colname = &quot;_&quot;.join(cols)
            df_cc[colname] = df_cc[list(cols)].apply(lambda x: &quot;-&quot;.join(x), axis=1)
            crossed_colnames.append(colname)
        return df_cc[crossed_colnames]

    def _prepare_wide(self, df: pd.DataFrame):
        if self.crossed_cols is not None:
            df_cc = self._cross_cols(df)
            return pd.concat([df[self.wide_cols], df_cc], axis=1)
        else:
            return df.copy()[self.wide_cols]</code></pre>
<ul>
<li>각각의 값들에 대한 look up table을 만드는 방식으로 진행됩니다</li>
<li>결과적으로 X_wide라는 array가 생성되며 이는 look up table에 매칭된 정수값으로 치환된 값들로 채워집니다</li>
<li>wide col과 cross col을 입력으로 받습니다<ul>
<li>wide col으로는 [&quot;genres&quot;,&quot;country&quot;,&quot;grade&quot;]</li>
<li>corss col으로는 [(&quot;genres&quot;,&quot;country&quot;),(&quot;country&quot;,&quot;grade&quot;),(&quot;genres&quot;, &quot;grade&quot;)]</li>
</ul>
</li>
<li>look up table의 형태</li>
</ul>
<pre><code class="language-python">{&#39;genres_멜로/로맨스/드라마&#39;: 1,
 &#39;genres_액션&#39;: 2,
 &#39;genres_드라마&#39;: 3,
 ... 
 &#39;genres_grade_뮤지컬-15세 관람가&#39;: 915,
 &#39;genres_grade_전쟁-전체 관람가&#39;: 916,
 &#39;genres_grade_드라마/가족-전체 관람가&#39;: 917}</code></pre>
<ul>
<li>해당 결과를 look-up-table을 이용해 inverse하게 복원하면 다음과 같은 형태입니다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/b23df064-c341-48e0-b70e-52b8e8a82b28/image.png" alt=""></p>
<ul>
<li>사용은 다음과 같습니다. X_wide는 결론적으로 wide모델의 input값 입니다</li>
</ul>
<pre><code class="language-python">wide_preprocessor = WidePreprocessor(wide_cols=wide_cols, crossed_cols=crossed_cols)
X_wide = wide_preprocessor.fit_transform(train_df)</code></pre>
<h3 id="deep-preprocessor">Deep Preprocessor</h3>
<pre><code class="language-python">class DeepPreprocessor():
    def __init__(self, categorical_cols: List[str], continuous_cols: List[str]=None):
        self.categorical_cols = categorical_cols
        self.continuous_cols = continuous_cols

    def fit_transform(self, df: pd.DataFrame):
        copy_df = df.copy()

        for c in self.categorical_cols:
            le = LabelEncoder()
            copy_df[c] = le.fit_transform(copy_df[c])

        X_train_category = np.array(copy_df[self.categorical_cols])

        scaler = StandardScaler()
        X_train_continue = scaler.fit_transform(np.array(copy_df[self.continuous_cols]))

        return X_train_category, X_train_continue</code></pre>
<ul>
<li>category 데이터는 LabelEncoder를 사용하여 숫자로 바꿔주며, continuous한 데이터는 StandardScaler를 사용하여 정규화를 하였습니다</li>
<li>X_train_category, X_train_continue는 deep model의 input으로 사용됩니다</li>
</ul>
<h3 id="wide-model">Wide model</h3>
<pre><code class="language-python">class Wide:
    def __init__(self):
        self.wide_model = None

    def make_wide_model(self, X_wide: np.array) -&gt; &quot;WideModel&quot;:
        input_wide = Input(shape=(X_wide.shape[1],))
        emb_wide = Embedding(len(np.unique(X_wide))+1, 1, input_length=X_wide.shape[1])(input_wide)
        wide_model = Flatten()(emb_wide)

        self.wide_model = wide_model
        return self</code></pre>
<ul>
<li><p>pytorch로 구현된 패키지의 다음 부분을 참고하여 임베딩을 진행하였습니다</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/1e5138dc-9906-4ca8-b254-e9446e1fb842/image.png" alt=""></p>
</li>
</ul>
<h3 id="deep-model">Deep model</h3>
<pre><code class="language-python">class Deep:
    def __init__(self, categorical_cols: List[str], continuous_cols: List[str]=None, embed_dim_ratio: float=0.5):
        self.category_inputs = []
        self.continue_inputs = []
        self.categorical_cols = categorical_cols
        self.continuous_cols = continuous_cols
        self.deep_model = None
        self.embed_dim_ratio= embed_dim_ratio

    def make_deep_model(self, df: pd.DataFrame):
        copy_df = df.copy()
        category_embeds = []

        for i in range(len(self.categorical_cols)):
            input_i = Input(shape=(1,), dtype=&#39;int32&#39;)
            dim = len(np.unique(copy_df[self.categorical_cols[i]]))
            embed_dim = int(np.ceil(dim**self.embed_dim_ratio))
            embed_i = Embedding(dim, embed_dim, input_length=1)(input_i)
            flatten_i = Flatten()(embed_i)
            self.category_inputs.append(input_i)
            category_embeds.append(flatten_i)

        self.continue_inputs = Input(shape=(len(self.continuous_cols),))
        continue_dense = Dense(128, use_bias=False)(self.continue_inputs)

        concat_embeds = concatenate([continue_dense] + category_embeds)
        concat_embeds = Activation(&#39;relu&#39;)(concat_embeds)
        bn_concat = BatchNormalization()(concat_embeds)

        fc1 = Dense(512, use_bias=False)(bn_concat)
        relu1 = ReLU()(fc1)
        bn1 = BatchNormalization()(relu1)
        fc2 = Dense(256, use_bias=False)(bn1)
        relu2 = ReLU()(fc2)
        bn2 = BatchNormalization()(relu2)
        fc3 = Dense(128)(bn2)
        relu3 = ReLU()(fc3)

        self.deep_model = relu3

        return self</code></pre>
<ul>
<li><p>category 데이터에 대해서 임베딩을 진행할 때, emb_dim차원은 default로 0.5로 진행하였습니다</p>
<ul>
<li><p>pytorch로 구현된 패키지에서는 다음과 같이 사용하고 있습니다</p>
<pre><code class="language-python">if embedding_rule == &quot;google&quot;:
      return int(round(n_cat**0.25))
  elif embedding_rule == &quot;fastai_old&quot;:
      return int(min(50, (n_cat // 2) + 1))
  else:
      return int(min(600, round(1.6 * n_cat**0.56)))</code></pre>
</li>
</ul>
</li>
<li><p>continue_dense, category_embeds을 concat한 뒷 fully connected layer를 통과하는 과정을 진행합니다</p>
</li>
</ul>
<h3 id="wide-and-deep">Wide and Deep</h3>
<pre><code class="language-python">class WideDeep:
    def __init__(self, wide: Wide, deep: Deep, activation_func: str = &quot;sigmoid&quot;):
        self.wide_instance = wide 
        self.deep_instance = deep
        self.activation_func = activation_func

    def make_wide_deep_model(self):
        out_layer = concatenate([self.deep_instance.deep_model, self.wide_instance.wide_model])
        inputs = [self.deep_instance.continue_inputs] + self.deep_instance.category_inputs + [self.wide_instance.wide_model]
        output = Dense(1, activation=self.activation_func)(out_layer)
        model = Model(inputs=inputs, outputs=output)

        return model</code></pre>
<ul>
<li>Wide와 Deep 객체를 input으로 받아 최종 wide_deep model을 구성합니다</li>
<li>최종적으로 sigmoid함수를 통과시키는 Dense를 통해 해당 아이템을 좋아할 확률값을 예측하게 됩니다</li>
</ul>
<pre><code class="language-python">wide = Wide().make_wide_model(X_wide)
deep = Deep(categorical_cols, continuous_cols).make_deep_model(train_df)
model = WideDeep(wide, deep).make_wide_deep_model()</code></pre>
<pre><code class="language-python">Model: &quot;model&quot;
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 input_2 (InputLayer)           [(None, 1)]          0           []                               

 input_3 (InputLayer)           [(None, 1)]          0           []                               

 input_4 (InputLayer)           [(None, 1)]          0           []                               

 input_5 (InputLayer)           [(None, 1)]          0           []                               

 embedding_1 (Embedding)        (None, 1, 14)        2492        [&#39;input_2[0][0]&#39;]                

 embedding_2 (Embedding)        (None, 1, 7)         343         [&#39;input_3[0][0]&#39;]                

 embedding_3 (Embedding)        (None, 1, 3)         27          [&#39;input_4[0][0]&#39;]                

 dense (Dense)                  (None, 128)          128         [&#39;input_5[0][0]&#39;]                

 flatten_1 (Flatten)            (None, 14)           0           [&#39;embedding_1[1][0]&#39;]            

 flatten_2 (Flatten)            (None, 7)            0           [&#39;embedding_2[1][0]&#39;]            

 flatten_3 (Flatten)            (None, 3)            0           [&#39;embedding_3[1][0]&#39;]            

 concatenate (Concatenate)      (None, 152)          0           [&#39;dense[1][0]&#39;,                  
                                                                  &#39;flatten_1[1][0]&#39;,              
                                                                  &#39;flatten_2[1][0]&#39;,              
                                                                  &#39;flatten_3[1][0]&#39;]              

 activation (Activation)        (None, 152)          0           [&#39;concatenate[1][0]&#39;]            

 batch_normalization (BatchNorm  (None, 152)         608         [&#39;activation[1][0]&#39;]             
 alization)                                                                                       

 dense_1 (Dense)                (None, 512)          77824       [&#39;batch_normalization[1][0]&#39;]    

 re_lu (ReLU)                   (None, 512)          0           [&#39;dense_1[1][0]&#39;]                

 batch_normalization_1 (BatchNo  (None, 512)         2048        [&#39;re_lu[1][0]&#39;]                  
 rmalization)                                                                                     

 dense_2 (Dense)                (None, 256)          131072      [&#39;batch_normalization_1[1][0]&#39;]  

 re_lu_1 (ReLU)                 (None, 256)          0           [&#39;dense_2[1][0]&#39;]                

 batch_normalization_2 (BatchNo  (None, 256)         1024        [&#39;re_lu_1[1][0]&#39;]                
 rmalization)                                                                                     

 dense_3 (Dense)                (None, 128)          32896       [&#39;batch_normalization_2[1][0]&#39;]  

 re_lu_2 (ReLU)                 (None, 128)          0           [&#39;dense_3[1][0]&#39;]                

 input_6 (InputLayer)           [(None, 6)]          0           []                               

 concatenate_1 (Concatenate)    (None, 134)          0           [&#39;re_lu_2[1][0]&#39;,                
                                                                  &#39;input_6[0][0]&#39;]                

 dense_4 (Dense)                (None, 1)            135         [&#39;concatenate_1[1][0]&#39;]          

==================================================================================================
Total params: 248,597
Trainable params: 246,757
Non-trainable params: 1,840
__________________________________________________________________________________________________</code></pre>
<p><img src="https://velog.velcdn.com/images/hong7_/post/c6d82a7f-6067-440d-87fa-c4bd4c8aab5f/image.png" alt=""></p>
<h3 id="학습">학습</h3>
<pre><code class="language-python">input_data = [X_train_continue] + [X_train_category[:, i] for i in range(X_train_category.shape[1])] + [X_wide]

epochs = 30
optimizer =&#39;adam&#39;
batch_size = 128

model.compile(optimizer=optimizer, loss=&#39;binary_crossentropy&#39;, metrics=[&#39;accuracy&#39;])
model.fit(input_data, train_target, epochs=epochs, batch_size=batch_size, validation_split=0.15, callbacks=[checkpoint, early_stopping])</code></pre>
<pre><code class="language-python">...
739/739 [==============================] - 19s 25ms/step - loss: 0.5216 - accuracy: 0.7729 - val_loss: 0.4955 - val_accuracy: 0.7843
Epoch 10/30
739/739 [==============================] - ETA: 0s - loss: 0.5176 - accuracy: 0.7737
Epoch 10: val_loss did not improve from 0.49554
739/739 [==============================] - 18s 25ms/step - loss: 0.5176 - accuracy: 0.7737 - val_loss: 0.5064 - val_accuracy: 0.7768
Epoch 11/30
739/739 [==============================] - ETA: 0s - loss: 0.5178 - accuracy: 0.7742
Epoch 11: val_loss improved from 0.49554 to 0.48547, saving model to ./checkpoint/wide-deep-keras.h5
739/739 [==============================] - 18s 25ms/step - loss: 0.5178 - accuracy: 0.7742 - val_loss: 0.4855 - val_accuracy: 0.7864
...</code></pre>
<h3 id="test">test</h3>
<pre><code class="language-python">wide_preprocessor = WidePreprocessor(wide_cols=wide_cols, crossed_cols=crossed_cols)
X_test_wide = wide_preprocessor.fit_transform(val_df)

deep_preprocessor = DeepPreprocessor(categorical_cols, continuous_cols)
X_test_category, X_test_continue = deep_preprocessor.fit_transform(val_df)

eval_input_data = [X_test_continue] + [X_test_category[:, i] for i in range(X_test_category.shape[1])] + [X_test_wide]

result = model.predict(eval_input_data)
df_val = pd.DataFrame([result.reshape(-1), val_target]).transpose()
df_val = df_val.rename(columns={0: &#39;predict&#39;, 1: &#39;real&#39;})</code></pre>
<p><img src="https://velog.velcdn.com/images/hong7_/post/2fd7a555-02dd-4dff-9e3c-c92b4917906b/image.png" alt=""></p>
<pre><code class="language-python">df_val[&#39;is_predict_over_half&#39;] = df_val[&#39;predict&#39;].apply(lambda x: 1 if x &gt;= 0.5 else 0)</code></pre>
<ul>
<li>예측 확률이 0.5 초과인 경우 값을 1로 대응하였습니다 
pytorch 패키지에서도 같게 처리한 것을 확인할 수 있었습니다</li>
</ul>
<pre><code class="language-python">if self.method == &quot;binary&quot;:
    preds = np.vstack(preds_l).squeeze(1)
    return (preds &gt; 0.5).astype(&quot;int&quot;)</code></pre>
<ul>
<li>이를 통해 실제 1인 값을 얼마나 맞췄는지에 대해서는 다음 값이 나왔습니다</li>
</ul>
<pre><code class="language-python">df_val[&#39;is_correct&#39;].sum() / df_val.shape[0]

0.6616619860301001</code></pre>
<pre><code class="language-python">loss, acc = model.evaluate(eval_input_data, val_target)

test_loss: 0.7483738660812378 - test_acc: 0.6616619825363159</code></pre>
<ul>
<li>구현 자체에 초점을 맞추어 어떤 col을 선택하여 wide와 deep에 넣을지에 대해서는 크게 신경쓰지 않았습니다</li>
<li>유저에 대한 정보가 input으로 들어가는 것이 없기 때문에 영화에 대략적인 정보만으로 평정을 예측하게 되었습니다. 따라서 개인 맞춤 추천이라고 보기는 힘들 것 같지만 구현 자체의 의미를 두었습니다 </li>
<li>추후 유저 개인의 정보까지 포함된 데이터 셋을 이용하여 적용해볼 예정입니다 </li>
<li>최종 코드는 다음 주소에 있습니다
<a href="https://github.com/hongchal/ml_study/blob/master/wide_and_deep_recommender/wide_and_deep_recommender_keras%20.ipynb">https://github.com/hongchal/ml_study/blob/master/wide_and_deep_recommender/wide_and_deep_recommender_keras .ipynb</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Wide & Deep Learning for Recommender System 논문 읽기 ]]></title>
            <link>https://velog.io/@hong7_/Wide-Deep-Learning-for-Recommender-System-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/Wide-Deep-Learning-for-Recommender-System-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</guid>
            <pubDate>Sun, 09 Jul 2023 08:50:27 GMT</pubDate>
            <description><![CDATA[<h1 id="wide--deep-learning-for-recommender-system">Wide &amp; Deep Learning for Recommender System</h1>
<h2 id="0-abstract">0. Abstract</h2>
<hr>
<ul>
<li>Wide : Memorization<ul>
<li>cross-product feature transformations 을 통해 feature간 interaction을 고려할 수 있다</li>
<li>more feature engineering effort</li>
</ul>
</li>
<li>Deep : Generalization<ul>
<li>generalize better to unseen feature combinations</li>
<li>less feature engineering</li>
<li>user-item inter- actions are sparse할 때, over-generalize and recommend less relevant items</li>
</ul>
</li>
<li>Wide &amp; Deep<ul>
<li>wide linear models 과 deep neural networks을 모두 사용하여 각각의 장점을 가져간다</li>
</ul>
</li>
</ul>
<h2 id="1-introduction">1. Introduction.</h2>
<hr>
<ul>
<li><p>추천 시스템은 유저 정보와 문맥 정보(contextual information)이 주워졌을 때 database에서 관련있는 item들을 찾고, 클릭이나 구매와 같은 특정 목표에 따라 순위를 매기는 search ranking system으로 간주될 수 있다</p>
</li>
<li><p>search ranking system에서는 Memorization과 Generalization을 모두 달성하는 것이 Challenge이다</p>
</li>
<li><p>Memorization</p>
<ul>
<li>item또는 feature들의 빈번한 동시발생(frequent co-occurrence)을 학습</li>
<li>과거 데이터에서 item또는 feature 사이의 상관관계를 활용</li>
<li>일반적으로 사용자가 이미 user가 특정 행동을 수행한 item과 직접적으로 관련이 있는 주제에 더 많은 추천을 제공</li>
</ul>
</li>
<li><p>Generalization</p>
<ul>
<li>과거에 거의 또는 전혀 발생하지 않은 새로운 특징 조합을 탐색</li>
<li>추천의 다양성을 개선</li>
<li>사용자의 다양한 관심사와 취향을 고려하여 사용자가 아직 알지 못한 새로운 항목을 추천</li>
</ul>
</li>
<li><p>Google Play Store의 app-recommendation실험을 했지만 일반적인 추천에도 사용할 수 있다</p>
</li>
<li><p>기존 모델의 한계</p>
<ul>
<li>Generalized Linear Model<ul>
<li>다양한 feature를 만들어야 하기 때문에 feature engineering에 들어가는 리소스가 크다</li>
<li>주어진 데이터에 대한 기억에 특화(일반화에 취약 - 새로운 또는 관측되지 않은 데이터에 취약하다)</li>
<li>오버피팅이 발생</li>
</ul>
</li>
<li>Embedding based Model<ul>
<li>새로운 또는 관측되지 않은 데이터에 잘 대응할 수 있다</li>
<li>Generalization에 특화</li>
<li>섬세한 추천이 불가하다</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="2-recommender-system-overview">2. Recommender System Overview</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/36b6468e-097d-43e8-b047-b045931f7012/image.png" alt=""></p>
<ul>
<li><p>Query(어떤 기기, 언제 접속 했는지, 어떤 앱을 원하고 있는지 etc..) 앱을 접속할 때 발생</p>
</li>
<li><p>조회 후 랭키화하여 10개의 상품을 추천</p>
</li>
<li><p>10개의 추천에 대해 유저의 action이 발생</p>
</li>
<li><p>Query, Items, User Action이 새로운 학습을 위한 데이터가 됨(Logs)</p>
</li>
<li><p>이를 Learner를 통해 학습하여 Model의 성능이 점점 좋아지게 된다</p>
</li>
<li><p>백만 개 이상의 앱이 있기 때문에, 서빙 지연 시간 요구 사항 (일반적으로 O(10) 밀리초) 내에서 모든 쿼리에 대해 모든 앱을 완전하게 점수화하는 것은 비실용적</p>
</li>
<li><p>따라서 쿼리를 수신하면 검색 시스템은 다양한 신호를 사용하여 쿼리와 가장 일치하는 항목의 짧은 목록을 반환</p>
</li>
<li><p>후보 풀을 줄인 후, 랭킹 시스템은 모든 항목을 점수별로 순위를 매긴다. 이 논문에서는 Wide &amp; Deep 프레임워크를 사용한 랭킹 모델에 초점을 맞춘다</p>
</li>
</ul>
<h2 id="3-wide--deep-learning">3. Wide &amp; Deep Learning</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/de17ae45-e5cb-43d7-88b8-082261212fc4/image.png" alt=""></p>
<h2 id="3-1-the-wide-component">3-1. The Wide Component</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/2d2b7fe3-bcf5-41be-a33d-9d6a0ee8d23d/image.png" alt=""></p>
<ul>
<li><p>y = wT x + b (Linear Model)</p>
</li>
<li><p>x → Raw input features &amp; cross-product feature</p>
</li>
<li><p>One of the most important transformations is the cross-product transformation</p>
<ul>
<li><p>선형대수의 cross-product와는 다른 개념</p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/82cb835d-8a5b-45da-b994-63adfc0dcc01/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<pre><code>- 사용자 = [남성, 20대]
- 영화 = [액션, 브래드 피트]
- Cross Product = [남성 x 액션, 남성 x 브래드 피트, 20대 x 액션, 20대 x 브래드 피트]
    - 각각의 값이 0 또는 1로 각 위치별 값도 0/1이 나온다</code></pre><h2 id="3-2-the-deep-component">3-2. The Deep Component</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ec981721-a0cf-4817-bb02-208b222e5355/image.png" alt=""></p>
<ul>
<li><p>기본적인 feed forward neural network</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/1cd8b083-9217-4e30-b3cc-61799c953ecf/image.png" alt=""></p>
</li>
</ul>
<pre><code>- activation = ReLu</code></pre><ul>
<li>faetures를 embedding과 뉴럴넷에 학습</li>
</ul>
<h2 id="3-3-joint-training-of-wide--deep-model">3-3 <strong>Joint Training of Wide &amp; Deep Model</strong></h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ae85810a-5fa8-433c-a676-9f82897dc404/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/322de65b-402a-4785-8889-e1e7cbe93179/image.png" alt=""></p>
<ul>
<li><p>joint training은 여러개의 모델을 결합하는 앙상블과 달리, output의 gradient를 wide와 deep모델에 동시에 back propagation하여 학습</p>
<ul>
<li>optimizer( Wide : Follow-the-regularized-leader(FTRL), Deep: AdaGrad)</li>
</ul>
</li>
<li><p>Wide</p>
<ul>
<li>cross-product의 결과를 input으로 사용하게된다</li>
</ul>
</li>
<li><p>Deep</p>
<ul>
<li>continuous feature와 임베딩 된 categorical feature를 concat하여 Deep의 input으로 사용하게 된다</li>
</ul>
</li>
<li><p>최종적으로 얻을 결과 값은</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/026c2666-84bb-4175-9a99-f99f97b2735f/image.png" alt=""></p>
</li>
</ul>
<pre><code>- Y is the binary class label
- σ(·) is the sigmoid function,
- φ(x) are the cross product transformations of the original features x
- a(lf ) is Deep model activation function
- W : weight
- X라는 feature일 때 앱을 다운받을 확률</code></pre><h2 id="4-system-implementation">4. System Implementation</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ca8923f4-cde5-4d01-8899-3374a233f4e7/image.png" alt=""></p>
<h2 id="4-1-data-generation">4-1. Data Generation</h2>
<hr>
<ul>
<li>일정 기간 동안의 사용자 및 앱 노출 데이터를 사용하여 학습 데이터 생성</li>
<li>레이블은 노출된 앱이 설치 되었으면 1, 설치되지 않았으면 0</li>
<li>vocabularies : categorical feature들을 정수형 변수로 매핑한 table</li>
<li>continuous 실수 feature들은 feature x를 누적 분포 함수에 매핑하여 [0,1]로 normalized</li>
</ul>
<h2 id="4-2-model-training">4-2. Model Training</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/12326ae9-243c-4502-85a0-652af811b024/image.png" alt=""></p>
<ul>
<li>각 categorical feature 마다 32 dimension embedding vector를 학습</li>
<li>5000억 개 이상의 예제들로 학습</li>
<li>새로운 훈련 데이터가 도착할 때 재훈련에 들어가는 비용과 제공 시간 지연을 최소화하기 위해서 warm start 시스템을 구축<ul>
<li>이전 모델에서 임베딩과 선형 모델 가중치를 가져와 새로운 모델을 초기화</li>
</ul>
</li>
</ul>
<h2 id="4-3-model-serving">4-3. Model Serving</h2>
<hr>
<ul>
<li>각 요청마다 앱 후보들과 사용자 특징을 받아 각 앱의 점수를 계산<ul>
<li>점수로 Rank화 하여 사용자에게 노출</li>
</ul>
</li>
<li>요청을 10ms(0.01) 정도의 처리 시간으로 처리하기 위해 모든 후보 앱을 한 번에 점수화하는 대신 작은 배치를 병렬로 실행하여 멀티스레딩 병렬성을 사용하여 성능을 최적화</li>
</ul>
<h2 id="5-experiment-results">5. Experiment Results</h2>
<hr>
<ul>
<li>we ran live experiments and evaluated the system in a couple of aspects: app acqui- sitions and serving performance.</li>
</ul>
<h2 id="5-1-app-acquisitions">5-1. <strong>App Acquisitions</strong></h2>
<hr>
<ul>
<li>A/B testing framework를 통해 3주동안 실험을 진행</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/53442e96-22f1-4d34-aba8-5f853910b792/image.png" alt=""></p>
<ul>
<li>Offline AUC<ul>
<li>Test Set의 사용자 행동결과로 성능 측정</li>
<li>AUC는 클수록 좋은 점수 (=ROC 커브의 아래 면적)</li>
</ul>
</li>
<li>Offline Acquisition Gain<ul>
<li>실제 사용자들의 action을 추적</li>
<li>기존 모델 대비 application 실제 다운로드 수가 증가</li>
</ul>
</li>
</ul>
<h2 id="5-2-serving-performance">5-2. <strong>Serving Performance</strong></h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/78497dd8-17ca-4994-b93d-59340e35987d/image.png" alt=""></p>
<ul>
<li>상용 모바일 앱 스토어에서는 높은 수준의 트래픽으로 인해 고 처리량과 낮은 지연 시간으로 서빙하는 것은 challenging(최대 트래픽 시간에는 추천 서버가 초당 1,000만 개 이상의 앱을 점수화)</li>
<li>단일 스레딩으로는 하나의 배치에서 모든 후보 앱의 점수화에 31 ms</li>
<li>멀티스레딩을 구현 지연 시간은 14 ms로 크게 감소</li>
</ul>
<h2 id="6-related-work">6. Related Work</h2>
<hr>
<ul>
<li>Wide와 Deep model의 결합은 이전 연구인 factorization machines에서 영감을 받았다<ul>
<li>이 논문에서는 내적(dot product) 대신 신경망을 사용하여 임베딩 간의 비선형 상호 작용을 학습하는 방법을 적용했다</li>
</ul>
</li>
<li>본 연구에서는 피드포워드 신경망과 선형 모델을 함께 훈련하여 희소한 입력 데이터를 다루었다</li>
<li>협업 필터링(CF) 및 컨텐츠 기반 접근법과 다른 방식으로, Wide &amp; Deep 모델을 사용하여 사용자와 impression data(노출 데이터)를 함께 훈련시키는 앱 추천 시스템에 대한 연구가 진행</li>
</ul>
<h2 id="7-conclusions">7. Conclusions</h2>
<hr>
<ul>
<li>Wide &amp; Deep Learning<ul>
<li>Linear model과 embedding-based model의 장점을 잘 조합하였다</li>
<li>Wide : sparse한 feature interaction 효과적으로 기억</li>
<li>Deep : 저차원 임베딩을 통해 이전에 볼 수 없었던 feature interaction 생성</li>
</ul>
</li>
<li>추천 알고리즘을 실제 서비스 환경에서 작동할 수 있도록 구현함</li>
<li>open sourse로 tensorflow api를 구현하여 다양하게 활용 가능하게 하였다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Neural Collaborative Filtering 논문 읽기 ]]></title>
            <link>https://velog.io/@hong7_/Neural-Collaborative-Filtering-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/Neural-Collaborative-Filtering-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</guid>
            <pubDate>Sun, 09 Jul 2023 08:40:44 GMT</pubDate>
            <description><![CDATA[<h1 id="neural-collaborative-filtering">Neural Collaborative Filtering</h1>
<h3 id="background">Background</h3>
<hr>
<ul>
<li><p>Contents Based Filtering</p>
<ul>
<li>유저가 아이템 A를 좋아할 때, A와 비슷한 특징을 갖는 아이템을 추천해주는 방식<ul>
<li>ex) 장르가 비슷하거나, 감독이 똑같거나 등등</li>
</ul>
</li>
</ul>
</li>
<li><p>Collaborative Filtering</p>
<ul>
<li><p>사용자의 행동 양식(평점, 구매이력) 을 기반으로 하여 추천</p>
<ul>
<li>ex) 사람들의 평정을 보고 영화를 볼지 안볼지 결정하는 것과 유사</li>
</ul>
</li>
<li><p>최근접 이웃 기반(nearest neighbor collaborative filtering)</p>
<ul>
<li>사용자 기반 (user based collaborative filtering) 
: 사용자의 구매 패턴(평점)과 유사한 사용자를 찾아서 추천 리스트 생성
( 유저 A와 B가 유사하다면, B가 좋아한 상품을 A도 좋아하겠지 )</li>
<li>아이템 기반 (item based collaborative filtering) 
: 특정 사용자가 준 점수간의 유사한 상품을 찾아서 추천 리스트 생성
( Item A와 Item B가 유사한 평점 분포를 가졌다면, B를 좋아한 유저는 A 상품도 좋아하겠지 )</li>
</ul>
</li>
<li><p>Latent Based Collaborative Filtering : Matrix Factorization</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/427042fd-5424-4e30-b6e3-e156406014bc/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<pre><code>    - SVD(특이값 분해) 의 응용
    - 주어진 사용자-아이템 상호작용 정보를 이용하여 사용자와 아이템의 잠재적인 특성을 추출한다
    - 새로운 아이템에 대해서도 (사용자와 아이템간의 상호작용이 없는) 예측 평점을 낼 수 있다
    - 차원 축소의 효과가 있다 (메모리 사용량을 줄이고 연산 속도를 향상 시킬 수 있다)</code></pre><h3 id="abstract"><strong>Abstract</strong></h3>
<hr>
<ul>
<li>DNN의 활용이 추천 시스템에서 큰 주목을 받지 못했다</li>
<li>implicit data를 기반으로 한 협업 필터링 문제에 대해 신경망 기반 기술을 개발하고자 노력</li>
<li>과거에도 추천에 DNN이 사용된 적은 있으나, 보조하는 정도로 사용되었으며 유저와 아이템의 관계에 대해서는  여전히 MF에 의존적</li>
<li>내적을 신경망 아키텍처로 대체함으로써 협업 필터링에 대한 NCF(Neural network-based Collaborative Filtering)이라는 일반적인 프레임워크를 제안</li>
<li><strong>비선형성을 강화</strong>하기 위해 NCF 모델링에는 다층 퍼셉트론(multi-layer perceptron)을 활용하여 사용자-아이템 상호작용 함수를 학습</li>
</ul>
<h3 id="1introduction">1.<strong>Introduction</strong></h3>
<hr>
<ul>
<li>Matrix Factorization은 netflix prize 이 후 가장 인기가 많은 추천 방법이 되었다</li>
<li>하지만 Matrix Factorization의 한계가 존재한다<ul>
<li>내적은 사용자와 아이템의 잠재 요인을 선형적으로 결합하는 방식이기 때문에, 사용자와 아이템 간의 복잡한 상호작용 구조를 제대로 포착하지 못할 수 있다</li>
</ul>
</li>
<li>이 논문에서는 비선형성을 줄 수 있는 DNN을 이용하여 협업 필터링에 접근한다</li>
<li>수집하기 쉬운 implicit feedback만 사용하였다 (noisy implicit feedback signal)</li>
</ul>
<h3 id="2-preliminaries">2. <strong><strong>Preliminaries</strong></strong></h3>
<hr>
<h3 id="2-1-learning-from-implicit-data">2-1. <strong>Learning from Implicit Data</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/5fca74fa-531d-4d77-882c-31aa3f93cca4/image.png" alt=""></p>
<ul>
<li><p>Implicit feedback은 상호작용 정보가 관측되었을 경우 1, 관측되지 않은 경우는 0</p>
</li>
<li><p>값이 1이면 사용자 u와 항목 i 간에 상호작용이 있다(선호를 의미하지는 않는다)</p>
</li>
<li><p>값이 0인 경우에도 u가 i를 좋아하지 않는다는 것을 의미하지는 않는다(사용자가 항목을 인식하지 못하는 것일 수도 있다 - 결측)</p>
</li>
<li><p>선호에 대한 노이즈가 많이 포함되어 있으며, 부정적인 피드백이 부족하고 결측 데이터가 많기 때문에 정확한 예측이 어렵다.</p>
<ul>
<li>따라서 관측되지 않은 항목의 점수를 예측하는 문제가 된다</li>
</ul>
</li>
<li><p>이런 문제를 풀 때 보통 다음과 같은 loss(point wise loss function, pairwise loss function)가 사용되며, 논문에서는 모두를 이용한다</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/43f59536-c8aa-4408-bb2c-1c24b79a95fc/image.png" alt=""></p>
</li>
</ul>
<h3 id="2-2-matrix-factorization">2-2. Matrix Factorization</h3>
<ul>
<li>MF는 아래 식과 같이 예측 값을 p와 q 벡터의 내적으로 계산하게 된다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/1b607bc7-b430-477e-877d-c5f9ef7e2252/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/ee24147b-e2e9-49a7-9848-57926312ceb0/image.png" alt=""></p>
<ul>
<li>Matrix Factorization에는 한계가 존재한다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/4579597c-56dc-4d81-8d77-c758663ff3a9/image.png" alt=""></p>
<ul>
<li>MF는 사용자와 아이템을 동일한 잠재 공간에 매핑하기 때문에 두 사용자 간의 유사성도 내적으로 측정할 수 있다</li>
<li>잠재 벡터 사이의 코사인 유사도를 측정할 수 있다</li>
<li>(a)에서 s23(0.66) &gt; s12(0.5) &gt; s13(0.4) 임을 알 수 있다 이는 (b)에서도 잘 반영된다<ul>
<li>2,3인 가장 유사하고, 그 다음은 1,2이 유사, 그 다음은 1과 3이 유사</li>
</ul>
</li>
<li>(a)에서 새로운 유저 4가 추가 되었을 때 s41(0.6) &gt; s43(0.4) &gt; s42(0.2) 로 계산된다<ul>
<li>4는 1과 제일 유사하며, 그 다음으로 3, 2 순으로 유사하다</li>
</ul>
</li>
<li>하지만 (b)에 표현된 벡터를 봤을 때, 4가 3보다 2에 더 가까움을 알 수 있다</li>
<li>저차원 잠재 공간에서 복잡한 사용자-아이템 상호작용을 추정하기 위해 단순하고 고정된 내적을 사용하는 MF의 가능한 한계</li>
<li>본 논문에서는 데이터로부터 DNN을 사용하여 상호작용 함수를 학습함으로써 이러한 한계를 해결</li>
</ul>
<h3 id="3neural-collaborative-filtering">3.<strong>Neural Collaborative Filtering</strong></h3>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/87300fd7-20f1-4bbe-8c39-c0f1801c2925/image.png" alt=""></p>
<h3 id="3-1-general-framework">3-1. <strong>General Framework</strong></h3>
<ul>
<li><p>Input one-hot vector -&gt; Embedding layer -&gt; Neural collaborative layer -&gt; Output layer 의 구조</p>
</li>
<li><p><strong>input one-hot vector</strong></p>
<ul>
<li>순수한 협업 필터링 설정에 초점을 맞추기 때문에, 입력값으로 사용자와 아이템의 신원(identity)한 원핫 인코딩 벡터가 들어간다</li>
</ul>
</li>
<li><p><strong>Embedding layer</strong></p>
<ul>
<li><p>임베딩 레이어는 희소한 표현을 밀집 벡터로 변환하기 위한 fully connected layer, embedding vector값은 MF관점에서 잠재 벡터와 비슷한 맥락</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/100556e6-3f8d-498b-9bb9-f59a9dbff9d9/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Neural collaborative layer</strong><ul>
<li>user latent vector 와 item latent vector를 concatenation한 벡터를 input으로 받아 deep neural network 통과하는 단계</li>
</ul>
</li>
<li><strong>Output layer</strong><ul>
<li>0과 1 사이의 값을 출력한다</li>
</ul>
</li>
</ul>
<h3 id="3-1-1-learning-ncf">3-1-1. <em>Learning NCF</em></h3>
<ul>
<li><p>이진 데이터로 구성되어있기 때문에 다음과 같은 likelihood function을 얻을 수 있다</p>
<ul>
<li><p>동전 던지기와 유사</p>
</li>
<li><p>관측 데이터로 부터 모델을 가장 잘 설명할 수 있는 파라미터 값을 찾는 것</p>
</li>
<li><p>likelihood function 최대가 되는 것이 모델을 제일 잘 설명하는 파라미터 값을 찾은 것</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/198c7e56-d1fe-4c4b-9ebe-f4ada8deba16/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>손실 함수는 해당 likelihood에 negative logarithm을 적용하여 다음과 같다</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/a96cdd74-78b0-419e-bc70-e35800eba436/image.png" alt=""></p>
</li>
</ul>
<pre><code>- 해당 값을 최소로 하는 것이 likelihood function 최대
- binary cross-entropy loss와 같은 수식
    - 실제값이 1이고 예측 값이 1이면 L값은 0 , 실제값이 1이고 예측값이 0 이면 -무한</code></pre><h3 id="3-2-generalized-matrix-factorization-gmf">3-2. <strong>Generalized Matrix Factorization (GMF)</strong></h3>
<ul>
<li><p>MF는 NCF의 특별한 케이스임을 설명</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/beaea374-ff5a-47dc-aa29-748a93a8b5b0/image.png" alt=""></p>
</li>
</ul>
<pre><code>![](https://velog.velcdn.com/images/hong7_/post/140bd978-65ff-4824-9be6-4b85457c3429/image.png)</code></pre><ul>
<li>p와 q를 latent vector라고 했을 때, 이를 element-wise product 한 값에 가중치(h)를 내적하고 activation function을 거친다</li>
<li>여기서 h가 uniform vector고 a1이면 MF이 된다</li>
<li>논문에서 사용되는 GMF는 a가 sigmoid func이고, h값은 uniform vector가 아닌 값이된다</li>
</ul>
<h3 id="3-3-multi-layer-perceptron-mlp">3-3. <strong>Multi-Layer Perceptron (MLP)</strong></h3>
<ul>
<li>GMF는 linear하고 fixed한 특징으로 인해 user 와 item간의 복잡한 관계를 표현하지 못함</li>
<li>MLP는 non-linear하고 flexible 하기 때문에 보다 복잡한 관계를 표현할 수 있다</li>
<li>user, item embedding vector를 concatenate</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/de43af6a-0fd0-4c7c-8a1e-7658fbd1a642/image.png" alt=""></p>
<ul>
<li>활성화 함수로는 ReLU 사용</li>
</ul>
<h3 id="3-4-fusion-of-gmf-and-mlp">3-4. <strong>Fusion of GMF and MLP</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/2f33ae61-cd25-4c08-bc95-63308f8c3c3c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d3728ca3-5b0e-4dc3-8e21-8e8f4450b748/image.png" alt=""></p>
<ul>
<li>각 모델별로 서로 다른 embedding layer를 사용(두 벡터의 차원이 다를 수 있다)</li>
<li>user-item간의 상호 관계를 표현하기 위해 MF의 linearity 와 MLP의 non-linearity를 결합한 것이 특징(neural matrix factorization<strong>)</strong></li>
</ul>
<h3 id="4experiments">4.<strong>Experiments</strong></h3>
<hr>
<ul>
<li><strong>RQ1.</strong> NCF가 그 당시의 SOTA를 능가할 수 있는가<strong>?</strong></li>
<li><strong>RQ2.</strong> 제안한 log loss with negative sampling optimization framework가 추천시스템 task에서 효과가 있는가?</li>
<li><strong>RQ3.</strong> user-item interaction 데이터로 학습을 하는 데 깊은 layers가 도움이 되는가?</li>
</ul>
<h3 id="4-1-experimental-settings">4-1. <strong>Experimental Settings</strong></h3>
<ul>
<li>MovieLens의 ml-1m dataset과 Pinterest의 dataset을 사용</li>
<li>Baseline Models<ul>
<li>ItemPop : non-personalized</li>
<li>ItemKNN : standard item-based collaborative filtering</li>
<li>BPR : pairwise ranking loss를 가지고 MF 모델을 optimize</li>
<li>eALS : MF 최신 기술</li>
</ul>
</li>
</ul>
<h3 id="4-2-performance-comparisonrq1">4-2. <strong>Performance Comparison(RQ1)</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/6d194cf3-edae-446a-b7f5-dc28cc7f90dc/image.png" alt=""></p>
<ul>
<li>HR@10 : 10개 중에 몇개를 맞추었는지(hit)를 의미하는 지표입니다.</li>
<li>NDCG : 랭킹 추천에 많이 쓰이는 지표로, 이상적인 정답 랭킹과 예측 랭킹을 비교하는 지표입니다.</li>
<li>NeuMF가 모든 부분에 있어 최고 성능을 냈다</li>
</ul>
<h3 id="4-3-log-loss-with-negative-samplingrq2">4-3. <strong>Log Loss with Negative Sampling(RQ2)</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/084a3989-c12a-412b-a70c-34fe96ad5ced/image.png" alt=""></p>
<ul>
<li>학습이 진행됨에 따라 loss가 지속적으로 감소하고, Loss값도 가장 낮다(implicit data에 대해서 log loss자체가 loss로서 괜찮다는 것을 시사)</li>
<li>number of negatives(positive instance당 negative instance 수) 에 따른 학습 결과에서도 높은 성능을 보였다</li>
<li>다만 number of negatives가 높아짐에 따라 전체적인 성능이 떨어짐을 알 수 있다</li>
<li>implicit data의 경우 negative data에 노이즈가 있기 때문에 학습에 사용하기 어려움이 있는데 이를 고려하여 성능을 보여주기 위함이 아니였을까</li>
</ul>
<h3 id="4-4-is-deep-learning-helpful-rq3">4-4. <strong>Is Deep Learning Helpful? (RQ3)</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/8b0615da-2d8f-421f-8a57-35ea753669ce/image.png" alt=""></p>
<ul>
<li>층이 깊어질수록 성능이 좋은 것을 알 수 있다</li>
<li>collaborative recommendation에서 deep models을 사용하는 것이 유의미</li>
<li>실제로 activation function으로 ReLU 대신 Identity를 써봤더니 성능이 훨씬 떨어졌다<ul>
<li>non-linear layers가 쌓임에 따라 더 높은 수준의 비선형성이 생긴다</li>
</ul>
</li>
<li>hidden layer가 없는 MLP-0의 경우 ItemPop 수준의 성능이 나온다. 따라서 은닉층을 통해 적절히 비선형성을 주는 것이 성능에 도움을 준다</li>
</ul>
<h3 id="5-related-work">5. <strong>Related Work</strong></h3>
<ul>
<li>초기에는 explicit data에 초점을 맞춘 추천 시스템 연구가 주를 이루었으며, 최근에는 implicit data에 대한 관심이 증가</li>
<li>이러한 추세에 맞추어 implicit data갖는 추천 문제를 해결하기 위해 여러 가지 전략과 모델들이 제안되었으며, 이 중 일부는 신경망을 사용하여 구현되었다</li>
<li>NeuMF가 MF와 MLP를 결합하는 아이디어는 부분적으로 NTN에서 영감을 받았지만, NeuMF는 MF와 MLP가 다른 임베딩 세트를 학습할 수 있는 측면에서 더 유연하다</li>
<li>최근 Google은 앱 추천을 위한 Wide &amp; Deep learning 접근 방식은 임베딩에 MLP를 사용하는데, 이는 강력한 일반화 능력을 갖고 있다고 보고된다</li>
<li>NeuMF는 순수한 협업 필터링 시스템을 위해 DNN을 탐구하는 것에 초점을 맞추었다.</li>
<li>DNN이 사용자-항목 상호작용을 모델링하는 데 유망한 선택지이다</li>
</ul>
<h3 id="6-conclusion"><strong>6. Conclusion</strong></h3>
<ul>
<li>NCF는 신경망을 사용하는 general한 협업필터링 framework 로서, 간단하게 다양한 모델들을 표현</li>
<li>MF의 일반화 형태인 GMF와 MLP를 융합하여 NeuCF를 제안하였으며, 기존의 모델에 비해 우수한 성능을 달성</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLO 논문 읽기 ]]></title>
            <link>https://velog.io/@hong7_/YOLO-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/YOLO-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</guid>
            <pubDate>Sun, 09 Jul 2023 08:33:59 GMT</pubDate>
            <description><![CDATA[<h1 id="yoloyou-only-look-once">YOLO(You Only Look Once)</h1>
<ul>
<li>Unified, Real-Time Object Detection<ul>
<li>Unified: 통합</li>
</ul>
</li>
</ul>
<h3 id="1-abstart">1. Abstart</h3>
<ul>
<li><p>기존 object dection의 방식이 아닌 regressions 문제로 접근한다</p>
</li>
<li><p>이미지 전체에 대해 하나의 신경망이 한 번의 계산으로 bounding box와 class probability를 예측한다</p>
</li>
<li><p>전체 detection pipeline이 단일 네크워크인 end-to-end 방식이다</p>
</li>
<li><p>매우 빠르다</p>
</li>
<li><p>초당 45 frame의 실시간 이미지를 처리할 수 있다 (45FPS)</p>
</li>
<li><p>fast YOLO의 경우 다른 real time detector들보다 2배의 mPA 성능을 보인다</p>
<ul>
<li>) </li>
</ul>
</li>
<li><p>FPS (Frame Per Second) : 1초당 몇 이미지 Frame이 보여지는지</p>
<ul>
<li>인간은 보통 25 FPS를 보면 영상이 끊기지 않는다고 판단(실시간성이 있다고 판단한다</li>
</ul>
</li>
<li><p>mPA (mean Average Precision) :</p>
<ul>
<li>정확도</li>
</ul>
</li>
</ul>
<h3 id="2-introduction">2. Introduction</h3>
<ul>
<li>현재(논문시점) object detection은 분류기(classifier)를 목적에 맞게 바꾸며 detection을 수행한다</li>
<li>객체를 감지하기 위해 분류기(classifier)를 사용하는데 분류를 진행한 후 객체를 탐지하는 복잡한 파이프라인을 거치기 때문에 속도가 느리고 최적화에 어려움이 있다<ul>
<li>DPM , R-CNN</li>
</ul>
</li>
<li>YOLO는 Object detection을 하나의 regression문제로 정의한다</li>
<li>single Conv net이 여러 bounding box와 클래스 확률을 동시에 계산한다</li>
<li>YOLO의 장점<ul>
<li>빠르다<ul>
<li>회귀 문제로 정의하면서 pipeline이 간단해졌다</li>
<li>25 milliseconds 미만의 지연 시간으로 실시간 스트리밍 비디오를 처리할 수 있다</li>
</ul>
</li>
<li>이미지 전체를 사용한다<ul>
<li>YOLO는 전체 이미지를 보기 때문에 클래스에 대한 문맥적 정보와 외형을 인코딩할 수 있다</li>
<li>background가 object라고 판단하는 background error를 줄일 수 있다</li>
</ul>
</li>
<li>일반적인 부분을 학습한다<ul>
<li>자연 이미지로 학습하고 art work(예술 작품)을 테스트할 때 월등히 뛰어났다</li>
</ul>
</li>
</ul>
</li>
<li>YOLO의 단점<ul>
<li>최신 SOTA 객체 검출 모델에 비해 정확도가 약간 떨어진다<ul>
<li>빠르게 객체를 식별할 수는 있지만, 작은 객체의 정확한 위치를 추론하는데는 어려움을 겪는다</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="2-1-unified-detection">2-1. <strong>Unified Detection</strong></h3>
<ul>
<li><p>YOLO는 객체 검출의 개별 요소를 단일 신경망(single neural network)로 통합한 모델이다</p>
</li>
<li><p>입력 이미지를 S x S grid로 나눈다</p>
</li>
<li><p>만약 어떤 객체의 중심이 특정 grid cell 안에 위치한다면 해당 grid cell이 객체를 검출해야한다</p>
</li>
<li><p>각 grid cell은 B개의 bounding box와 그에 대한 confidence score를 예측한다</p>
<ul>
<li><p>confidence score : 객체를 포함하고 있다고 확신하는 정도와 </p>
<pre><code>                           예측한 bounding box가 얼마나 정확한지</code></pre><p><img src="https://velog.velcdn.com/images/hong7_/post/673735f0-e194-4030-bc16-58195c7b728e/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<pre><code>- IOU</code></pre><p>   <img src="https://velog.velcdn.com/images/hong7_/post/d5011799-dd43-4a95-b353-236afffaafdc/image.png" alt=""></p>
<pre><code>- mAP@0.5 = 정답과 예측의 IOU가 50%이상일 때 정답으로 판정하겠다는 의미
- Pr(object) = 0 : grid cell에 아무 객체가 없다, 어떤 객체가 확실히 있다고 예측했을 때 = 1
- 각각의 bounding box는 5개의 예측치로 구성된다
    - x,y : bounding box 중심의 gird cell 내 상대위치 (0 ~ 1 사이 값)

        ![](https://velog.velcdn.com/images/hong7_/post/5af89fe3-a079-4e76-9bf8-01a2e89f0db7/image.png)


    - w, h : bounding box의 상대 너비와 상대 높이 ⇒ 전체 이미지 대비 w, h (0~1 사이 값)
        - Q) 왜 상대적인 값을 사용했을까?
    - confidence score
- 각각의 grid cell은 conditional class probabilities(C) 를 예측한다
    - cell안에 객체가 있다는 조건 하에 어떤 class 인지를 나타내는 조건부 확률
        - Q) 애초에 아무것도 없음이라는 class가 없고, 하나의 class를 무조건 예측하도록 만들었을까?
    - cell 에 몇개의 bounding box가 있는지와 무관하게 하나의 cell은 하나의 class에 대한 확률만 갖는다
- 테스트 단계에서는 grid cell의 C와 개별 bounding box에 confidence값을 곱한 class-specific confidence socre를 사용한다

    ![](https://velog.velcdn.com/images/hong7_/post/755b1f77-2f45-45ea-ac6a-59d73685bc02/image.png)

    - bounding box에 특정 클래스 객체가 나타날 확률과 예측된 bounding box가 그 class 객체에얼마나 fit하게 맞았는지
- 논문에서 최종 예측 텐서의 차원은 (S x S x (B*5 + C)) =  7 * 7 * 20</code></pre><h3 id="2-2-network-design">2-2. <strong>Network Design</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/36516336-6194-44f6-bd1e-60e2635a8727/image.png" alt=""></p>
<ul>
<li>전체 구조는 24개의 Conv계층과 2개의 FC 계층으로 구성된다, 1*1 layer는 이전 feature space를 줄여준다</li>
<li>Conv 계층은 이미지로부터 특징을 추출</li>
<li>FC 계층은 클래스 확률과 bounding box의 좌표를 예측</li>
<li>신경망의 구조는 GoogLeNet 구조에 영감을 받아 설계</li>
<li>GoogLeNet의 inception 구조대신 1<em>1 축소 계측과 3</em>3 컨볼루션 계층을 결합하여 사용</li>
</ul>
<h3 id="2-3-training">2-3. Training</h3>
<ul>
<li><p>ImageNet dataset으로 앞 단의 20개의 Conv계층을 사전 훈련시킨다</p>
</li>
<li><p>사전 훈련된 20개의 Conv계층 뒤에 4개의 Conv계층과 2개의 전결합 계층을 추가한다</p>
</li>
<li><p>마지막 계층에는 Linear activation funtion을 적용하고, 나머지 모든 계층에는 leaky ReLu를 적용</p>
<ul>
<li><p>leaky ReLu</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/3d2e3494-d4f2-4dac-a5d0-80dc75967a2f/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>YOLO의 loss는 SSE(sum-squared error)를 기반으로 한다</p>
</li>
<li><p>SSE를 최적화하는 것이 YOLO의 최종 목적인 mPA를 높히는 것과 완전히 일치하지는 않는다</p>
<ul>
<li>YOLO의 loss는 localization lossd(bounding box위치 예측 정도)와 classification loss</li>
<li>가중치를 동일하게 두고 학습하는 것은 좋은 방법이 아니지만 SSE를 최적화하는 방식은 두 loss의 가중치를 동일하게 취급</li>
</ul>
</li>
<li><p>배경 영역이 더 크기 때문에 대부분의 grid cell에 confidence score=0이 되어 모델의 불균형이 생긴다</p>
<ul>
<li>이는 YOLO가 모든 cell에 대해서 confidence=0으로 예측하도록 학습되게 할 수 있다</li>
</ul>
</li>
<li><p>이를 개선하기 위해 객체가 존재하는 bounding box 좌표에 대한 loss의 가중치를 증가 시키고, 객체가 존재하지 않는 box에 대해서는 감소시킨다</p>
<ul>
<li>이는 localization loss의 가중치를 증가시키고, 객체가 존재하는 cell의 confidence loss의 가중치를 증가시킨다</li>
<li>λ_coord=5, λ_noobj=0.5로 가중치를 부여<ul>
<li>λ_coord : 객체를 포함하는 grid cell에 곱해주는 가중치</li>
<li>λ_noobj : 객체를 포함하지 않는 grid cell에 곱해주는 가중치</li>
</ul>
</li>
</ul>
</li>
<li><p>SSE는 큰 bounding box와 작은 bounding box를 모두 동일한 가중치로 loss를 계산한다</p>
<ul>
<li>작은 bounding box는 작은 위치 변화에도 객체를 벗어날 수 있다</li>
<li>너비와 높이에 square root를 취해 높이와 너비가 큰 값에 대해서 가중치를 감소시킨다</li>
</ul>
</li>
<li><p>YOLO는 하나의 grid cell이 여러 개의 bounding box를 예측하는데, 결국엔 객체 하나당 하나의 bounding box가 선택되어야 한다</p>
<ul>
<li>이를 위해 여러 bounding box중에서 실제 객체를 감싸는 box(ground-truth boudning box)와 IOU가 가장 큰 것을 선택한다</li>
</ul>
</li>
<li><p>훈련 단계에서 사용하는 loss function</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/39083fd5-a43f-412f-b711-6c1dc4f51aac/image.png" alt=""></p>
</li>
</ul>
<pre><code>- S^2 = 모든 cell의 수, B = cell당 bounding box의 수
- 1_i^obj : cell 안에 객체가 존재하는지 여부
- 1_ij^obj : cell 의 i의 j번째 bounding box 가 사용되는지 여부
- 1 ) Object가 존재하는 i번 째 cell에 대해 사용하는 j번 째 box에 대해 x ,y loss
- 2) Object가 존재하는  i번 째 cell에 대해 사용하는 j번 째 box에 대해 w, h loss (큰 박스에 대해 작은 가중치를 주기 위해서 square root)
- 3) Object가 존재하는  i번 째 cell의 j번 째 box에 대해서 confidence loss (Ci =1 )
- 4) Object가 존재하지 않는 i번 째 cell의 j번째 box에 대해서  confidence loss (Ci = 0)
- 5) Object가 존재하는 i번 째 cell에 대해 conditional class probability</code></pre><ul>
<li>overfitting을 막기 위해 dropout과 dat augmentation(random scaling, random translation)을 적용</li>
</ul>
<h3 id="2-3-inference">2-3. Inference</h3>
<ul>
<li><p>하나의 신경망만 계산하면 되기 때문에 굉장히 빠르다</p>
</li>
<li><p>객체의 크기가 크거나, 객체가 cell의 경계에 인접해 있는 경우 객체에 대한 bounding box가 여래 개 생길 수 있다 (multiple detections)</p>
</li>
<li><p>NMS(non-maximal suppression)를 통해 mPA 2~3% 올릴 수 있었다</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/e674254c-aa09-4de4-bbe3-9e0d879e25da/image.png" alt=""></p>
</li>
</ul>
<h3 id="2-4-limitations-of-yolo">2-4. <strong>Limitations of YOLO</strong></h3>
<ul>
<li>하나의 cell에 하나의 객체만을 검출해야하므로 cell에 두개 이상의 객체가 붙어있다면 잘 검출하지 못함</li>
<li>훈련 단계에서 학습하지 못한 종횡비(aspect ration)를 테스트 단계에서 만났을 때 고전한다</li>
<li>큰 bounding box와 작은 bounding box에 대해서 동일한 가중치를 준다</li>
</ul>
<h3 id="3-comparison-to-other-detection-systems">3. <strong>Comparison to Other Detection Systems</strong></h3>
<ul>
<li><p>DPM (Deformable Parts Models)</p>
<ul>
<li><p>sliding window를 사용</p>
<p>  <img src="https://velog.velcdn.com/images/hong7_/post/ed41014e-57a5-42f4-be2c-c667d1477607/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<pre><code>- 특징 추출, 위치 파악, bounding box예측 등을 서로 분리된 파이프 라인으로 구성한다
- 하지만 YOLO는 하나의 파이프라인으로 구성되어있어 DPM보다 빠르다</code></pre><ul>
<li>R-CNN<ul>
<li>sliding window 대신 selective search 방식을 사용<ul>
<li>여러 bounding box를 생성하고, 컨볼루션 신경망으로 feature를 추출하고, SVM으로 bounding box에 대한 점수를 측정</li>
</ul>
</li>
<li>linear 모델로 bounding box를 조정하고, NMS로 중복 검출 제거</li>
<li>복잡한 파이프라인을 단계별로 독립적으로 튜닝해야하기 때문에 굉장히 느리다</li>
<li>정확성은 높지만 속도가 느려서 실시간 객체 검출로 사용하기엔 한계가 있다</li>
<li>grid cell이 여러 개의 bounding box를 생성하고, 이에 점수를 계산하는 것이 YOLO와 비슷하다<ul>
<li>하지만 YOLO는  98개 R-CNN은 2000개로 YOLO가 훨신 적은 bounding box로 진행 가능</li>
</ul>
</li>
<li>YOLO는 모든 과정이 하나의 파이프라인</li>
</ul>
</li>
<li><strong>Other Fast Detectors</strong><ul>
<li>Fast R-CNN과 Faster R-CNN은 R-CNN 프레임워크를 개선하지만 실시간 속도에는 아직 부족</li>
<li>DPM 파이프라인의 속도를 높이는 노력이 있었지만, 실시간으로 동작하는 DPM은 30Hz DPM 뿐이다</li>
<li>하나의 파이프라인으로 구성된 YOLO가 더 빠르다</li>
</ul>
</li>
<li>그 외 Deep MultiBox, OverFeat, MultiGrasp 와의 비교</li>
</ul>
<h3 id="4-experiments">4. <strong>Experiments</strong></h3>
<ul>
<li>YOLO를 다른 실시간 객체 검출 모델과 비교</li>
<li>파스칼 VOC 2007 dataset 사용</li>
</ul>
<h3 id="4-1-comparison-to-other-real-time-systems">4-1. <strong>Comparison to Other Real-Time Systems</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d537d759-1c62-4526-a3dd-6776d811b6d5/image.png" alt=""></p>
<ul>
<li>객체 검출의 많은 연구들이 표준화된 객체 검출을 빠르게 만드는데 초점을 두고있다</li>
<li>Real-Time Detectors(FPS 30이상) 중에서 YOLO가 가장 mAP가 높다<ul>
<li>Fast YOLO는 YOLO에 비해 mAP는 조금 떨어지지만(그래도 52.7로  30Hz DPM의 2배 가량의 성능)<br>VOC dataset에 대해서 가장 빠른 모델이다</li>
</ul>
</li>
<li>YOLO VGG-16은 mAP는 높아졌지만 FPS가 낮아, 실시간 객체 검출 모델로 사용하기에 느리다</li>
<li>Fastest DPM은 mAP를 약간 하락시키며 FPS를 높였지만 여전히 실시간 객체 검출 모델로 사용하기에 느리다</li>
<li>정확도도 높고 실시간 처리가 가능한 빠른 모델은 YOLO</li>
</ul>
<h3 id="4-2-voc-2007-error-analysis">4-2. <strong>VOC 2007 Error Analysis</strong></h3>
<ul>
<li>객체 검출이 정확한지, 틀렸다면 어떤 error인지</li>
<li>YOLO와 Fast R-CNN 을 비교</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/8255102b-09ac-4f53-986b-615eb5d43b50/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d50dce04-f2d2-468b-8676-0f4460518504/image.png" alt=""></p>
<ul>
<li>Correct : class가 정확하며 IOU &gt; 0.5 인 경우</li>
<li>Localization : class가 정확하고,  0.1 &lt; IOU &lt; 0.5 인 경우</li>
<li>Similar : class가 유사하고 IOU &gt; 0.1 인 경우</li>
<li>Other : class는 틀렸으나, IOU &gt; 0.1 인 경우</li>
<li>Background : 어떤 Object라도 IOU &lt; 0.1 인 경우</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/32d3a601-0341-422e-935c-fd55417be6e7/image.png" alt=""></p>
<ul>
<li>YOLO는 localization error가 가장 높고, Fast R-CNN에 비해서도 localization error가 높다</li>
<li>반면에 R-CNN은 background error가 가장 높고, YOLO에 비해서도 background error가 높다</li>
</ul>
<h3 id="4-3-combining-fast-r-cnn-and-yolo">4-3. <strong>Combining Fast R-CNN and YOLO</strong></h3>
<ul>
<li>서로의 약점을 보완하는 방식으로 둘 모델을 결합한다면 높은 성능을 낼 수 있을 것</li>
<li>Fast R-CNN 은 파스칼 VOC 2007 dataset에 대해서 mAP가 가장 높은 71.8 mAP를 기록하였다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/e36ab874-d892-4971-9cd2-f7266d8b7bb1/image.png" alt=""></p>
<ul>
<li>다른 모델과의 앙상블보다 YOLO와 앙상블 했을 때 증가율이 3.2로 가장 높았다</li>
<li>Fast R-CNN 과 YOLO를 독립적으로 실행하여 앙상블하는 방식이기 때문에 YOLO에 비해느리지만,
YOLO가 워낙 빨라 Fast R-CNN을 단독으로 돌리는 것과 거의 비슷한 속도를 낸다</li>
</ul>
<h3 id="4-4-voc-2012-results">4-4. <strong>VOC 2012 Results</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/4d6a3baa-bdab-4256-b74f-95fc469a211a/image.png" alt=""></p>
<ul>
<li>YOLO 다른 모델보다 작은 객체에 어려움을 겪는다</li>
<li>병, 양, TV/모니터와 같은 카테고리에서 YOLO의 점수는 R-CNN이나 Feature Edit보다 8-10% 낮다.</li>
<li>고양이와 기차와 같은 다른 카테고리에서는 YOLO가 더 높은 성능을 달성</li>
<li>Fast R-CNN + YOLO가 높은 순위권에 위치</li>
</ul>
<h3 id="4-5-generalizability-person-detection-in-artwork">4-5. <strong>Generalizability: Person Detection in Artwork</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/79ce8f1b-f912-48e3-8d83-f2d58d489b2f/image.png" alt=""></p>
<ul>
<li>훈련 단계(실제 이미지)에서 보지 못했던 피카소 데이터 셋과 일반 예술 작품 데이터 셋을 이용하여 테스트</li>
<li>R-CNN은 VOC 2007에서는 높은 정확도를 보이지만 예술작품에 대해서는 굉장히 낮은 정확도</li>
<li>DPM은 예술 작품에 대해서도 정확도가 크게 떨어지지는 않지마 VOC 2007에서의 정확도도 높은 편은 아님</li>
<li>YOLO는 3개 영역에서 높은 성능을 보임</li>
</ul>
<h3 id="5-real-time-detection-in-the-wild">5. <strong>Real-Time Detection In The Wild</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/a54e55c1-e72e-469f-b63b-92968027a5c6/image.png" alt=""></p>
<h3 id="6-conclusion">6. <strong>Conclusion</strong></h3>
<ul>
<li>YOLO는 단순하면서도 빠르다</li>
<li>YOLO는 훈련 단계에서 보지 못한 새로운 이미지에 대해서도 객체를 잘 검출한다<ul>
<li>애플리케이션에서도 충분히 활용할만한 가치가 있다</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transformer 논문 읽기]]></title>
            <link>https://velog.io/@hong7_/Transformer-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</link>
            <guid>https://velog.io/@hong7_/Transformer-%EB%85%BC%EB%AC%B8-%EC%9D%BD%EA%B8%B0</guid>
            <pubDate>Sun, 25 Jun 2023 06:53:35 GMT</pubDate>
            <description><![CDATA[<h1 id="attention-is-all-you-need-transformer">Attention Is All you Need (Transformer)</h1>
<ul>
<li>2018, Google Researcher</li>
</ul>
<h2 id="소개">소개</h2>
<hr>
<ul>
<li>입력된 <strong>시퀀스</strong>로부터 다른 도메인의 <strong>시퀀스</strong>를 출력</li>
<li><strong>seq2seq</strong>[<a href="https://arxiv.org/pdf/1409.3215.pdf%5D">https://arxiv.org/pdf/1409.3215.pdf]</a>, 2014
→ <strong>attention</strong>[<a href="https://arxiv.org/abs/1409.0473%5D">https://arxiv.org/abs/1409.0473]</a>, 2014
→ <strong>transformer</strong>
→ ** BERT나 GPT 모두 트랜스포머를 기반으로 한 분류/생성 모델 **</li>
</ul>
<h3 id="seq2seq"><strong>seq2seq?</strong></h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/9357c0e8-2b28-4c8b-87b3-8aed75d15f5e/image.png" alt=""></p>
<ul>
<li>context vector = <strong>RNN 셀의 마지막 시점의 은닉 상태</strong></li>
<li>문제점<ul>
<li>입력 시퀀스의 내용을 하나의 벡터에 모두 담으려다 보니 정보 손실이 발생한다 <strong>(long-term dependencies problem)</strong></li>
<li>RNN의 고질적인 그레디언트 소실 이슈</li>
</ul>
</li>
</ul>
<h3 id="-attention">** attention?**</h3>
<ul>
<li>입력 시퀀스가 길어지면 출력 시퀀스의 정확도가 떨어지는 것을 보정하기 위해 등장 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/1dafc201-5fc6-4dc2-95d2-225cc77eb801/image.png" alt=""></p>
<ul>
<li>Query에 대해서 모든 Key와의 유사도를 구하고, 이를 키에 맵핑되어있는 Value에 반영하고 이를 모두 더하여 Attention Value를 구함</li>
<li>Dot Product Attention</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/b1b31b2d-b960-4bd4-bd1c-8ba22a0cc915/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/5ef680cb-e2a6-4124-8112-49bc93063de8/image.png" alt=""></p>
<ul>
<li>Q = Query : t 시점의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/547a3190-f595-4beb-8662-65537129aeb6/image.png" alt=""></p>
<ul>
<li>Q*K 을 softmax에 통과시켜 모든 값을 합하면 1이 되는 확률 분포를 얻음 -  어텐션 분포<ul>
<li>decoder t시점의 출력에 얼만큼 영향을 미칠지</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/f0a67407-89e7-41b0-a9b1-295c69f88111/image.png" alt=""></p>
<ul>
<li><p>해당 값들을 각 시점의 인코더 은닉상태에 곱하여 합하면 attention value</p>
</li>
<li><p>예측하고자 하는 시점에 은닉 상태를 이용해 입력 시퀀스에 어떤 단어와 연관이 가장 높은지</p>
</li>
<li><p>여전히 RNN을 사용해서 문제가 완전히 해결된 것은 아님</p>
<ul>
<li>병렬 처리 제약 → 하드웨어 리소스를 효과적으로 사용하지 못함</li>
</ul>
</li>
</ul>
<h2 id="0-abstarct">0. Abstarct</h2>
<hr>
<ul>
<li>주류인 시퀀스 모델들은 RNN, CNN으로 구성되어있고 우수한 모델들은 encoder와 decoder를 attention mechanism을 통해 연결</li>
<li>RNN, CNN을 완전히 제거하고 Attention만을 이용하여 새롭고 간단한 아키텍처인 Transformer를 제안</li>
<li>실험결과 <strong>더 우수한 성능을 보이면서</strong> <strong>병렬화가 가능하고 학습에 더 적은 시간이 소요</strong></li>
</ul>
<h2 id="1-introduction">1. Introduction</h2>
<hr>
<ul>
<li>RNN은 LSTM과 GRNN을 포함하여 고도화되고, 이 후 인코더와 디코더 아키텍처의 한계를 넓히기 위해 노력</li>
<li>하지만 RNN은 그 이전 상태(ht-1)과 t 시점의 입력을 통해 t시점의 은닉상태(ht)를 생성하기 때문에 병렬처리가 불가</li>
<li>최근 연구를 통해 긴 시퀀스 길이에서 발생하는 문제는 해결했지만 순차적인 계산 문제를 해결하지 못함</li>
<li>Attention을 통해 입력 또는 출력의 길이 문제는 해결했지만, 여전히 병렬처리는 불가(RNN사용)</li>
<li>본 논문에서는 RNN을 배제하고 입력과 출력 간의 dependency를 어텐션만을 사용, <strong>병렬화</strong>를 가능하게 함</li>
</ul>
<h2 id="2-background">2. Background</h2>
<hr>
<ul>
<li>sequential computation을 줄이는 것은 CNN을 이용한 Extended Neural GPU, ByteNet, ConvS2S에서도 다뤄졌으나, 이 모델들에서는 입력과 출력을 연과시키는데 필요한 연산 수가 많다</li>
<li>Transformer에서는 Multi-Head Attention를 이용하여 연산을 줄였다</li>
<li>Self-attention은 시퀀스의 서로 다른 위치를 관련시켜 시퀀스의 표현을 계산하기 위한 메커니즘</li>
<li>Transformer는 RNN이나 convolution을 사용하지 않고 온전히 self-attention에 의존한 최초의 변역 모델</li>
</ul>
<h2 id="3-model-architecture">3. <strong>Model Architecture</strong></h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/2abcb2e2-98e5-4f4f-ac79-973eaac11473/image.png" alt=""></p>
<ul>
<li>주류인 시퀀스 변환 모델들은 대부분 인코더 - 디코더 구조를 따르고, Transformer도 이를 따른다</li>
<li>Transformer는 self-attenstion과 fully connected layer를 이용하여 인코더와 디코더를 구성한다</li>
</ul>
<h3 id="3-1-encoder-and-decoder-stacks">3-1. <strong>Encoder and Decoder Stacks</strong></h3>
<ul>
<li><strong>encoder</strong><ul>
<li>논문에서는 Encoder는 6개 층으로 stack</li>
<li>각 layer는 두 개의 sub-layer가 있음<ul>
<li>첫 번째 sub-layer는 multi-head self-attention mechanism</li>
<li>두 번째 sub-layer는 간단한 position-wise fully connected feed-forward network</li>
</ul>
</li>
<li>two sub-layers 마다 residual connectio 후에 layer normalization 적용</li>
<li>즉 각 sub-layer의 결과는 LayerNorm(x+Sublayer(x)) 임</li>
<li>잔차연결(residual connection)을 구현하기 위해, embedding layer를 포함한 모든 sub-layer들의 output은 512 차원</li>
</ul>
</li>
<li><strong>decoder</strong><ul>
<li>논문에서는 Decoder 또한 6개 층으로 stack</li>
<li>인코더와 다르게, 인코더 각 stack의 출력에 대해 multi head attention을 수행하는 층이 추가
<img src="https://velog.velcdn.com/images/hong7_/post/90dc69a4-f518-4b67-8f25-d90e7b83610d/image.png" alt=""></li>
<li>인코더와 마찬가지로 sub-layer에 잔차연결(residual connection)과 정규화 연산<ul>
<li>마스킹을 이용하여 i위치의 예측은 i보다 이전 위치 출력만 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="3-2-attention">3-2. Attention</h3>
<ul>
<li>query(Q), key(K), value(V) 쌍을 입력으로 받아 출력을 생성하는 함수</li>
</ul>
<h3 id="3-2-1-scaled-dot-product-attention">3-2-1. Scaled Dot-Product Attention</h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/951e4982-33ac-4224-ac8b-0ec74ee0b407/image.png" alt=""></p>
<ul>
<li>모든 쿼리와 key에 대해 dot product를 계산하고, √dk 로 나눠주고, weight을 적용하기 위해 value에softmax함수를 적용</li>
<li>내적 어텐션(Dot-Product)의 속도가 훨씬 빠르고 메모리 공간을 효율적으로 사용할 수 있다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/d7d4d61c-7bd3-400e-b13f-95a61395e143/image.png" alt=""></p>
<ul>
<li><p>SoftMax 함수에서 기울기가 극히 작아질 수 있기 때문에 스케일링을 진행한다</p>
</li>
<li><p>1개의 입력 벡터에 대해서 대략 다음과 같은 행렬 연산이 일어나고, 
Q벡터가 나오면, 이를 모든 K벡터와 내적한 값을 정규화하여 softmax를 통과시킨다 
그 뒤에 V벡터에 곱하여 모두 합하여 Attention Value를 계산하다 </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/69f52274-d5cd-4de1-85e8-4ec9efe5a6e7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/7633f547-5cf0-4a93-bd1d-25d46f957d99/image.png" alt=""></p>
<ul>
<li>위 과정 중 Q,K,V 벡터의 계산을 한 번에 병렬처리하면 대략 다음 이미지와 같다 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/7821fb59-7825-4473-ad20-8dd44f9e84fa/image.png" alt=""></p>
<h3 id="3-2-2-multi-head-attention">3-2-2. Multi-Head Attention</h3>
<p><img src="https://velog.velcdn.com/images/hong7_/post/1cddd0b9-4e66-48f3-bb9d-c7bcefb61c85/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hong7_/post/9d7e072a-0206-4075-815d-d4ee0c787817/image.png" alt=""></p>
<ul>
<li>한 번의 어텐션보다 여러 번의 어텐션을 병렬적으로 하는 것이 더 효과적</li>
<li>논문에서는 8개로 병렬처리</li>
<li>논문에서는 dmodel 차원이 512 이고 num head = 8이므로 W 벡터는 (512, 64)<ul>
<li>64 = 512 / 8</li>
<li>나중에 병렬처리한 attention벡터들을 concatenate했을 때 dmodel과 차원이 같음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/88655e02-dcef-4423-b975-df85074bc192/image.png" alt=""></p>
<ul>
<li>입력 차원과 동일하게 맞추는 이유는 인코더/디코더가 여러 층으로 stack되기 때문에 다음 입력으로 넘겨주기 위해서 결과적으로 출력도 입력과 동일한 차원</li>
</ul>
<h3 id="3-2-3-applications-of-attention-in-our-model">3-2-3. Applications of Attention in our Model</h3>
<ul>
<li>self-attention in encoder <strong>:</strong> 해당 position과 모든 position간의 correlation information</li>
<li>self-attention in decoder : masking vector를 사용하여 해당 position 이전의 벡터들만을 참조</li>
<li>encoder-decoder attention: decoder의 sequence vector들이 encoder의 sequence vector들과 어떠한 correlation</li>
</ul>
<h3 id="3-3-position-wise-feed-forward-networks">3-3. Position-wise Feed-Forward Networks</h3>
<ul>
<li>fully connected feed-forward network</li>
<li>ReLu를 포함하여 총 2개의 선형 변함이 포함</li>
<li>마찬가지로 출력은 512차원이며, layer마다 서로 다른 매개변수를 사용한다</li>
</ul>
<h3 id="3-4-embeddings-and-softmax">3-4. Embeddings and Softmax</h3>
<ul>
<li>다른 sequence transduction models 처럼, 학습된 임베딩을 사용함</li>
<li>decoder output을 예측된 다음 토큰의 확률로 변환하기 위해 선형 변환과 softmax를 사용함</li>
</ul>
<h3 id="3-5-positional-encoding">3-5. Positional Encoding</h3>
<ul>
<li>어떤 recurrene, convolution도 사용하지 않기 때문에, sequence의 순서를 사용하기 위해 sequence의 상대적 또는 절대적인 position에 대한 정보필요</li>
<li>• 다양한 positional encoding 방법 중 sine, cosine function을 사용하여 위치 정보를 생성</li>
</ul>
<h2 id="4-why-self-attention">4. Why Self-Attention</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hong7_/post/df7a2392-ff2e-4eba-b6ac-e3a084144de1/image.png" alt=""></p>
<ul>
<li>layer당 시간 복잡도<ul>
<li>n : sequence length, d : representation dimensionality<ul>
<li>d: 대부분의 경우 기계 번역에서 사용되는 문장 표현 방식인 단어 조각</li>
<li>sequence의 길이가 전체 단어의 수보다 긴 경우가 드물다 ( n &lt; d 가 대부분)</li>
</ul>
</li>
</ul>
</li>
<li>Sequential Operation<ul>
<li>self-attention layer는 input의 모든 position 값들을 연결하여 한번에 처리<ul>
<li>반면 RNN의 경우 t시점에 대해서 알기위해서는 t-1까지 계산을 차례로 거처야함</li>
</ul>
</li>
</ul>
</li>
<li>Maximum Path Length<ul>
<li>length of paths란 forward와 backward signals간의 길이</li>
<li>self-attention은 각 token들을 모든 token들과 참조하여 그 correlation information을 구해서 더해주기 때문에(심지어 encoder-decoder끼리도), maximum path length를 O(1)</li>
</ul>
</li>
</ul>
<h2 id="6-results">6. Results</h2>
<hr>
<ul>
<li>기계 번역 (Machine Translation)<ul>
<li>Transformer 모델은 WMT 2014 영어-독일어 및 영어-프랑스어 번역 작업에서 기존의 모델들을 능가하는 우수한 성능, 더 작은 모델 크기로도 경쟁 모델보다 우수한 번역 결과를 달성</li>
</ul>
</li>
<li>언어 모델링 (Language Modeling): 데이터셋의 크기와 모델의 크기에 관계없이 일관된 성능 향상</li>
<li>Transformer 모델은 번역 작업 외에도 음성 인식과 어순 변환 작업에서도 강력한 성능</li>
<li>긴 문장 처리에 대한 실험에서도 LSTM 기반 모델보다 더 효과적인 결과</li>
</ul>
<p>참고 
<a href="%5D(https://www.youtube.com/watch?v=AA621UofTUA)">https://www.youtube.com/watch?v=AA621UofTUA</a>
<a href="https://wikidocs.net/31379">https://wikidocs.net/31379</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2년간의 스타트업 개발자 회고 ]]></title>
            <link>https://velog.io/@hong7_/%ED%9A%8C%EA%B3%A0-2%EB%85%84%EA%B0%84%EC%9D%98-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@hong7_/%ED%9A%8C%EA%B3%A0-2%EB%85%84%EA%B0%84%EC%9D%98-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 08 Jun 2023 09:06:55 GMT</pubDate>
            <description><![CDATA[<h3 id="왜-회고를-하나">왜 회고를 하나?</h3>
<p>&#39;지금 내가 잘 하고 있나?&#39;를 묻기 시작하고, 그 시작점에서 이제까지의 일들을 돌아보는 시간을 가져야겠다고 생각했다. 또 더욱 시간이 지나 기억이 흐려지거나, 왜곡되지 않도록 하기 위해서 기록을 남기고자했다. 혼자 메모장에 적어 남기는 것보다 누군가에게 설명하거나 보여줄 수 있을 정도로 정리해야 시간이 지나고 나에게도 도움이 된다고 생각하여 글로 정리하게 되었다. </p>
<h3 id="어떻게-시작하게-되었나">어떻게 시작하게 되었나?</h3>
<p>대학 4학년을 올라가면서 실무 경험을 쌓고 싶어 학점 교류 인턴을 시작으로 일을 시작하게 되었다. 
학교에서 배움도 좋았지만 정말 개발자로 일하고 싶은지 의문이 있어 이를 해소하고 싶었다.</p>
<h3 id="지난-2년은-어땠나">지난 2년은 어땠나?</h3>
<p>처음 들어갔을 때는 현재와 다르게 비대면 피트니스 서비스 이미 진행 중에 있었다. 6개월쯤 후에 해당 아이템을 피벗하고, 현재 진행 중인 다이어트 식단 추천 서비스를 시작하게 되었고 초기 기획부터 PMF, MVP를 찾는 실험들과 오픈배타 진행과 A시리즈 투자를 경함하고 운영 단계를 앞두고 있다. </p>
<p>2년간 했던 업무들을 돌아보면 &#39;필요해서 했다&#39;라고 정리가 된다. 여기서 필요해서 했다는 수동적 의미에서 필요한 업무만 했다가 아니다. 일을 시작하면서 느낀 건, 모든 것은 비즈니스의 성장으로 이어져야 한다는 것이었다. 따라서 기획적으로 정의된 일 외에도 팀 전체의 업무 효율을 높이기 위해 문제가 되는 것들을 정의하고 해결해야 하는 환경이었다. 투입한 리소스 대비 좋은 성과를 거두어 소득이 아닌 이윤에 초점을 맞추게 되었다.</p>
<h3 id="그래서-나는-어떤-업무를-했나">그래서 나는 어떤 업무를 했나?</h3>
<p><strong>개발 업무 (시간 역순)</strong></p>
<ul>
<li><p>데이터 수집 파이프 라인 구축 
  <strong>why?</strong> 
  기존에도 필수라고 생각한 api요청에 대해서는 RDB에 저장해두고 사용했다. 하지만 모든 데이터를 로그로 기록하고 있지 않아(업데이트 방식) 중간 변화 데이터가 유실되었다. 뿐만 아니라 로그로 기록하고 있지 않았던 데이터들과 단순 버튼 클릭의 데이터도 수집하고 데이터 분석과 모델링에 인풋 혹은 라벨링에 사용할 목적이다. </p>
<p>  <strong>how?</strong>
  우선 데이터 파이프 라인에 대해서 아는 것이 없었기 때문에 빠르게 강의를 듣고 병렬적으로 staging서버에 적용해보며 업무를 진행했다. 결과적으로 kafka와 API gateway/kinesis 각각 써보고 지금 단계에서는 API gateway/kinesis가 초기 세팅이 더욱 편하고 cloudwatch를 이용해서 모니터링이 편하다는 점에서 선택하게 되었다 </p>
</li>
<li><p>추천 시스템 
  <strong>why?</strong>
  피벗을 하면서 기존 유저들이 다이어트 식단에 요구가 많음을 알게 되었다. 매번 사람이 맞춤 식단을 짜줄 수 없으므로 이를 해결할 추천 시스템이 필요했다. </p>
<p>  <strong>how?</strong>
  영양사님과 협업하여 식단에 필요한 영양적인 규칙 + 좋은 식단을 위한 규칙들을 정의하였다. 기성품으로 이뤄진 식단에 대한 평가 데이터가 없을 뿐만 아니라 최소한 적은 리소스를 들여 유저들의 반응을 보는 것이 우선이었다. 따라서 사전 정의된 &#39;끼니&#39;단위의 데이터를 RDB에 저장하여 Django ORM + python code로 이를 조합하는 방식을 통해 구현했다. 이후 유저 테스트/인터뷰를 진행하며 얻은 피드백과 추천 알고리즘을 참고하여 numpy array를 이용한 점수 시스템, 최근엔 deep learning으로 고도화하였다. (자세한 내용은 따로 다룰 예정이다)</p>
<p>  <strong>result?</strong>
  단계적으로 시스템을 고도화시킴에 따라 초기 테스트를 빠르게 진행할 수 있었다.
  마케팅을 진행하지 않았음에도 결제전환율이 10%대를 유지하며 2회차 재구매율이 50%을 달성할 수 있었다. 
  고도화를 통해 처음 5~6초 이상 서버 리소스를 잡아먹던 추천을 2초대로 줄일 수 있게 되었다. </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/eaf4020c-e367-4167-96d6-965240cb3b2d/image.png" alt=""></p>
<ul>
<li><p>물류/영업팀의 식품 입점을 위한 대시보드 
  <strong>why?</strong>
  추천의 성능은 보유하고 있는 식품에도 상당히 많은 영향을 받는다. 우선 최대한 많은 양의 식품을 보유하는 것이 좋긴 하지만 공헌이익 관점에서도 좋지 못하며(지금 상황에서) 대량 주문이 들어오는 것이 아니므로 입점을 유지하기 힘들다. 따라서 추천에 영향을 최소한으로 주는 정도의 식품을 보유해야하지만 어떤 종류의 상품을 입점하고 해지해야 할지 참고할 지표가 없어 의사결정을 감으로 해야 했다.</p>
<p>  <strong>how?</strong>
  이를 해소하기 위해서 식품 카테고리별 건전성 지표(지난 1달간의 판매/추천 데이터를 활용한)와 현재 보유중인 물품의 수를 비교하는 그래프를 제공하였다. 후에 카테고리 단위에서 머물지 않고, 상품단위의 최근 1달간의 추천 / 판매 정황을 볼 수 있는 표를 제공하였다. </p>
<p> <strong>result?</strong>
 물류/영업팀의 의사결정에 있어서 감이 아닌 데이터를 참조할 수 있는 환경을 구축하였다 </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hong7_/post/e0a5ca4f-ec9e-4f29-8b38-b57218abee80/image.png" alt=""></p>
<ul>
<li>그 외 기능 개발(api 작성), 영양 로직 통일 및 classmethod로 유틸화를 진행하여 메모리 누수 방지 </li>
<li>피벗 이전엔 front-end 개발자로 약 6개월의 시간을 보냈다(당시 서비스는 front end 일손이 많이 부족했기 때문) </li>
</ul>
<p><strong>백오피스</strong></p>
<ul>
<li><p>면접 일정 관리(일정 조율 및 메일링) 
  <strong>why?</strong> 
  이전 채용 플로우가 거의 없었으며, 채용 과정부터 좋지 않은 인상을 심어주는 것을 개선하고 싶었다 
  <strong>how?</strong> 
  채용 단계별로 필요한 메일 양식을 정하고 채용에 필요한 주의사황과 업무 플로우를 팀원들과 공유하였다 
  <strong>result?</strong>
  팀별로 채용 과정에서 업무 진행이 꼬이는 것을 방지할 수 있었다 
  각 팀 리더들의 채용관리에 들어가는 리소스를 줄여 중요한 의사결정에 집중할 수 있도록 도움을 주었다 
<img src="https://velog.velcdn.com/images/hong7_/post/7943a44e-2f54-4567-8b70-58076df2543b/image.png" alt=""></p>
</li>
<li><p>사내 온보딩 구축 
  <strong>why?</strong> 
  입사 이후에 첫인상에 대한 피드백을 듣고 이를 개선하고 싶었다 
  <strong>how?</strong>
  온보딩과 관련된 웨비나에 참석하여 다른 회사들의 온보딩 프로세스를 참고하였다 
  첫인상을 좋게 하기 위해 신입 사원의 자리를 미리 셋팅 + 팀 리더들의 손 편지를 제공하도록 하였다
  이름표를 작성하여 이름을 몰라 업무 요청에 딜레이가 생기는 것을 해소하였다 
  <strong>result?</strong> 
  신입사원들의 온보딩에 대한 부정적인 피드백이 사라졌다 
  업무에 빠르게 적응하는 데 도움을 줄 수 있었다 </p>
</li>
</ul>
<h3 id="kptkeepproblemtry">KPT(KeepProblemTry)</h3>
<ul>
<li><p>Keep </p>
<ul>
<li>기술에 집착하지 않고, 어떻게 문제를 풀어나갈 것인지 &#39;솔루션&#39;에 초점을 맞춘 것</li>
<li>왜 내가 이 일을 하고 있는지 스스로 질문하는 것 </li>
<li>팀원들에게 먼저 티타임을 요청하여 이야기할 시간을 갖는 것 </li>
<li>도움이 필요한 곳에 주도적으로 나서는 것 </li>
<li>체력관리를 위해서 아침 시간을 이용해 꾸준히 운동하는 것 </li>
<li>조금이라도 성능 향상을 하고자 집요하게 매달린 것 </li>
</ul>
</li>
<li><p>Problem </p>
<ul>
<li>업무 시간 외에 개인 공부 시간이 부족한 것 </li>
<li>개인 공부 및 업무에서 어떤 식으로 문제를 해결했는지 기록(아카이빙)을 하지 않은 것 </li>
<li>의존하지 않고자 너무 혼자서 일을 해결하려고 했던 것 </li>
<li>일에서 감정을 배재하지 못한 적이 있는 것 </li>
<li>약점 보완에만 너무 신경을 쓴 것 </li>
</ul>
</li>
</ul>
<ul>
<li>Try <ul>
<li>공부를 지속할 수 있도록 외부 스터디에 참여하는 것(부스트캠프, 모두의 연구소 등) </li>
<li>부족한 지식을 채울 수 있는 강의를 찾거나 추천받고 고정적으로 학습할 시간을 확보하여 회사 데이터를 가지고 혼자 사이드 프로젝트 진행 및 아카이빙 </li>
<li>업무에 있어서 모르거나 시간이 오래 걸릴 것 같을 때(감이 전혀 안 잡힐 때) 주변 팀원들에게 빠르게 물어보기 </li>
<li>감정에 동요가 있을 때 의사결정을 하지 않기</li>
<li>내 강점이 무엇인지 주변에 물어보고 스스로 인지하여 강화하기 </li>
</ul>
</li>
</ul>
<h3 id="느낀점">느낀점</h3>
<p>2년의 시간 동안 팀에 필요한 일 중에서도 내가 잘 할 수 있는 일을 하면서 조금씩 직무에 변화가 있었다. 지금에 와서는 앞으로 데이터와 관련하여 더 많은 일을 경험하고 성장하고 싶다. 데이터 직무와 근접한 업무들을 하며 얻었던 경험들 특히 팀원들에게 도움이 되고 내 존재 가치를 느낄 수 있는 순간들이 많았기 때문이다. 더욱 성숙하고 프로다운 개발자들을 만나며 많은 것을 배우고 싶다. 더 나아가서는 그런 사람이 되어 내가 쌓아온 경험으로 도움을 줄 수 있는 사람이 되고 싶다. 처음에는 일을 할 수 있다는 것에 감사하고 시간이 지나면서는 일하는 의미에 대해서 많은 질문들을 하게 되었다. 그럴 때마다 내 옆에 나보다 훨씬 뛰어난 동료들을 보면서 다시 마음을 다잡기도 했지만, 그들을 떠나갈 때마다 더 크게 흔들리기도 했다. 그러다 보니 &#39;여기 왜 있는가?&#39;에서 &#39;왜 스타트업을 하는가?&#39;로 질문이 넘어가게 되었다. 이 글을 정리하는 지금도 솔직히 두렵기도 하고 무섭기도 하다. 스스로 확답을 내릴 수는 없지만 지금 찍는 점 하나하나들이 나중에 이어지는 날이 있을 것이라 믿는다. </p>
]]></description>
        </item>
    </channel>
</rss>