<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>_cykim_.log</title>
        <link>https://velog.io/</link>
        <description>DS에 대한 고민과 해결을 글로 남기고자 합니다</description>
        <lastBuildDate>Fri, 30 Aug 2024 07:04:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>_cykim_.log</title>
            <url>https://velog.velcdn.com/images/_cykim_/profile/2ba494ef-ac00-4392-ad7a-48a8b91a20ed/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. _cykim_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_cykim_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Paper Review] LightGBM : A Highly Efficient Gradient Boosting Decision Tree]]></title>
            <link>https://velog.io/@_cykim_/LightGBM-A-Highly-Efficient-Gradient-Boosting-Decision-Tree</link>
            <guid>https://velog.io/@_cykim_/LightGBM-A-Highly-Efficient-Gradient-Boosting-Decision-Tree</guid>
            <pubDate>Fri, 30 Aug 2024 07:04:19 GMT</pubDate>
            <description><![CDATA[<p>Find-A에서 프로젝트 팀으로 데이터 분석팀 팀장을 하게 되었는데, 그 덕에 가끔씩 정규세션에 참여하여 관련해서 발표를 진행하게 되었다. 발표 주제를 LightGBM으로 정했는데, 많이 쓰는 모델이면서도 정확하게 어떻게 알고리즘이 동작하는지 알고 쓰는 사람이 많지는 않을 것 같다는 판단이었다. 나 또한 복습도 할 겸 좀 모델을 좀 더 깊게 들여다보았고, 특히 EFB 부분은 예전에는 그냥 &quot;아 Bundling 하는거구나&quot;하고 넘겼었는데 딥하게 이해하니까 상당히 재미있는 아이디어였다.</p>
<p>사실 이거 리뷰 포스팅을 하려고 하는데,,, 나름 PPT 열심히 만들었는데 그걸 놔두고 다시 쓰기 싫었다 ㅠㅠ (걍 발표자료 올려야겠다 ㅎ)</p>
<p>근데 velog에는 파일 업로드하는 기능이 없나보다.. <a href="https://chip-thought-5b0.notion.site/LightGBM-725e51c8cc4b4630b84f2cdea95e4df4">노션 링크</a>로 남긴다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Type Casting을 활용한 최적화(feat.대용량 데이터)]]></title>
            <link>https://velog.io/@_cykim_/Type-Casting%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%B5%9C%EC%A0%81%ED%99%94feat.%EB%8C%80%EC%9A%A9%EB%9F%89-%EB%8D%B0%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@_cykim_/Type-Casting%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%B5%9C%EC%A0%81%ED%99%94feat.%EB%8C%80%EC%9A%A9%EB%9F%89-%EB%8D%B0%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Fri, 09 Aug 2024 07:12:54 GMT</pubDate>
            <description><![CDATA[<p>직전 포스팅에 이어, 데이터를 보다 효율적으로 다룰 수 있는 또 다른 방법을 알아보자. int와 float을 활용한 최적화이다.</p>
<p>int(정수형)와 float(부동 소수점) 데이터 타입은 여러 종류가 있고, 각각 메모리 크기와 표현할 수 있는 값의 범위는 다르다.</p>
<p>예를 들어, int8은 메모리 사용량이 1byte인 반면 int16은 메모리 사용량이 2byte이다. 두 배 정도 차이난다. 같은 원리로, int8과 int64의 메모리 사용량은 각각 1byte와 8byte로 8배 정도 차이가 난다. <strong>만약 int8로도 충분히 표현 가능한 데이터임에도 불구하고 int64를 사용하고 있다면 쓸데없이 메모리를 낭비하고 있는 것이다.</strong> 이는 float도 마찬가지이다. float16은 2byte, float32는 4byte, float64는 8byte를 차지한다. </p>
<p>각 데이터 타입은 메모리 사용량과 값의 범위 사이에서 적절한 균형을 잡기 위해 선택된다. 매우 큰 정수를 처리하거나 높은 정밀도가 필요한 계산에서는 int64나 float64를 사용할 수 있으나, 메모리를 절약하고자 할때는 int16이나 float16을 사용하는게 적절할 것이다. 불필요하게 높은 메모리를 사용하고 있는 경우 Type Casting을 통해 메모리를 절약해보자.
꽤나 간단하다.</p>
<p>이전 포스팅과 같은 데이터를 사용하겠다.</p>
<p>데이터 및 참고 소스코드
<a href="https://github.com/s-heisler/pycon2017-optimizing-pandas/tree/master/pyCon%20materials">https://github.com/s-heisler/pycon2017-optimizing-pandas/tree/master/pyCon%20materials</a></p>
<h2 id="0-데이터-준비">0. 데이터 준비</h2>
<pre><code>import pandas as pd
import numpy as np

df = pd.read_csv(&#39;new_york_hotels.csv&#39;, encoding=&#39;cp1252&#39;)
print(df.shape)
df.head()</code></pre><p><img src="https://velog.velcdn.com/images/_cykim_/post/6827c489-1323-4556-854c-713fd164483d/image.png" alt=""></p>
<h2 id="1-형-변환-함수-사용">1. 형 변환 함수 사용</h2>
<p>object와 같은 데이터 타입은 그냥 넘기고, int와 float만 건들여준다. 원리는 현재 우리가 가진 데이터 내 최댓값과 최솟값을 구하고, 해당 범위에 해당하는 최적의 dtype을 적용시키는 것이다.</p>
<pre><code>def reduce_mem_usage(df):

    # 1MB = 1024KB = 1024Byte -&gt; 1M = 1024**2Byte
    start_mem = df.memory_usage().sum() / 1024**2
    print(&#39;Memory usage of dataframe is {:.2f} MB&#39;.format(start_mem)) # 초기 메모리 기록

    for col in df.columns: # 각 열을 순회
        col_type = df[col].dtype # 각 열의 데이터 유형 가져옴
        if str(col_type) in [&quot;category&quot;, &quot;object&quot;]: # category라면 다음 열로 넘어감
            continue
        else: # 수치형일 경우 -&gt; 최적화 진행
            # 해당 열의 최솟값과 최댓값 계산
            c_min = df[col].min()
            c_max = df[col].max()
            # 정수형일 경우(dtype의 첫 세 글자가 int)
            if str(col_type)[:3] == &#39;int&#39;:
                # int8 범위 내에 들어온다면 -&gt; 데이터 타입 int8로 변경
                if c_min &gt; np.iinfo(np.int8).min and c_max &lt; np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                # int16 범위 내에 들어온다면 -&gt; 데이터 타입 int16로 변경
                elif c_min &gt; np.iinfo(np.int16).min and c_max &lt; np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                # int32 범위 내에 들어온다면 -&gt; 데이터 타입 int32로 변경
                elif c_min &gt; np.iinfo(np.int32).min and c_max &lt; np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                # 어떠한 범위도 해당 안된다면 -&gt; 데이터 타입 int64로 변경
                else:
                    df[col] = df[col].astype(np.int64)
            else:
                # float16 범위 내에 들어온다면 -&gt; 데이터 타입 float16로 변경
                if c_min &gt; np.finfo(np.float16).min and c_max &lt; np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                # float32 범위 내에 들어온다면 -&gt; 데이터 타입 float32로 변경
                elif c_min &gt; np.finfo(np.float32).min and c_max &lt; np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                # 어떠한 범위도 해당 안된다면 -&gt; 데이터 타입 float64로 변경
                else:
                    df[col] = df[col].astype(np.float64)

    end_mem = df.memory_usage().sum() / 1024**2  # Memory usage after optimization
    print(&#39;Memory usage after optimization is: {:.2f} MB&#39;.format(end_mem)) # 변경 후 메모리 기록
    print(&#39;Decreased by {:.1f}%&#39;.format(100 * (start_mem - end_mem) / start_mem))</code></pre><h2 id="2-결과">2. 결과</h2>
<pre><code>reduce_mem_usage(df)
&gt;&gt;&gt; Memory usage of dataframe is 0.14 MB
&gt;&gt;&gt; Memory usage after optimization is: 0.08 MB
&gt;&gt;&gt; Decreased by 38.6%</code></pre><p>0.14MB를 차지하던 데이터가 0.08MB로 줄어들었다. 메모리를 38.6%나 절약한 상태로 데이터 분석을 진행할 수 있으니, 대용량 데이터를 다룬다면 이러한 방식의 데이터 최적화도 유용할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vectorization을 활용한 최적화(feat.대용량 데이터)]]></title>
            <link>https://velog.io/@_cykim_/Pandas-Vectorize</link>
            <guid>https://velog.io/@_cykim_/Pandas-Vectorize</guid>
            <pubDate>Sun, 04 Aug 2024 07:04:42 GMT</pubDate>
            <description><![CDATA[<p>인턴 시절 145만개 정도 되는 공정 데이터를 분석해야 하는 상황이 있었다. 당시 여러 공정 장비를 활용해서 파생변수를 만들어야 했는데, 데이터가 꽤 많다보니 시간이 상당히 걸렸었다. 그 때 데이터 전처리 시 최적화 기법을 찾아봤었고, 그 중 vectorization을 공부하여 지금도 유용하게 사용하고 있다. </p>
<p>만약 몇 천만 정도 되는 대용량 데이터를 사용한다면 반드시 알아야 하는 개념일 것이다(그 때도 apply()나 iterrows()를 사용하면 꽤 손해를 보게된다. 단순 반복문은 지옥이다).
한번 더 제대로 숙지할 겸, 당시 참고한 블로그를 활용해 실험 코드를 돌려보고 이를 정리해보겠다.  </p>
<blockquote>
<p>Vectorization의 원리를 알고 모르고의 차이는 상당하다. 숙지하는 것이 좋다.</p>
</blockquote>
<p>데이터 및 참고 소스코드
<a href="https://github.com/s-heisler/pycon2017-optimizing-pandas/tree/master/pyCon%20materials">https://github.com/s-heisler/pycon2017-optimizing-pandas/tree/master/pyCon%20materials</a></p>
<h2 id="0-데이터-및-함수-준비">0. 데이터 및 함수 준비</h2>
<pre><code>import pandas as pd
import numpy as np

df = pd.read_csv(&#39;new_york_hotels.csv&#39;, encoding=&#39;cp1252&#39;)
print(df.shape)
df.head()</code></pre><p><img src="https://velog.velcdn.com/images/_cykim_/post/6827c489-1323-4556-854c-713fd164483d/image.png" alt="">
해당 데이터의 크기와 생김새는 다음과 같다. 여기서 하고자 하는 것은 특정 점의 위도(latitude)와 경도(longtitude)를 활용해 지구의 곡률을 조정하고 일련의 거리를 계산하여 &quot;distance&quot;라는 파생변수를 생성하는 것이다.
함수는 다음과 같다.</p>
<pre><code>from math import *

# Haversine - 기본 거리 공식 정의. 지구 상에서 두 점의 직선 거리
def haversine(lat1, lon1, lat2, lon2):
    MILES = 3959
    lat1, lon1, lat2, lon2 = map(np.deg2rad, [lat1, lon1, lat2, lon2])
    dlat = lat2 - lat1 
    dlon = lon2 - lon1 
    a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
    c = 2 * np.arcsin(np.sqrt(a)) 
    total_miles = MILES * c
    return total_miles</code></pre><h2 id="1-단순-반복-for문">1. 단순 반복 for문</h2>
<p>모든 행을 수동으로 반복하며 일련의 거리를 반환하는 함수를 정의한다. 정말 일반적인 방법이며, 직관적이다. 판다스를 잘 모르는 상태에서 사용하기에 가장 쉬운 방법이다.
참고로 시간 측정은 매직 명령어인 <strong><em>%%timeit</em></strong>를 사용한다. 해당 셀의 가장 위에 위치해야 하는 것을 명심하자.</p>
<pre><code>%%timeit
# 모든 행을 수동으로 반복하며 일련의 거리를 반환하는 함수를 정의함
def haversine_looping(df):
    distance_list = []
    for i in range(0, len(df)): # 모든 행을 돌면서
         # 위도와 경도를 넣어서 (40.671, -73.985)와의 거리 계산
        d = haversine(40.671, -73.985, df.iloc[i][&#39;latitude&#39;], df.iloc[i][&#39;longitude&#39;])
        distance_list.append(d)
    return distance_list

df[&#39;distance&#39;] = haversine_looping(df)

&gt;&gt;&gt; 301 ms ± 81.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)</code></pre><p>약 301ms정도의 시간이 소요되었다. 약 1600개 행을 처리하는데 필요한 함수임을 고려하면 느린 편이다. 이를 개선해보자.</p>
<h2 id="2-iterrows를-사용한-반복">2. iterrows()를 사용한 반복</h2>
<p>iterrows()는 행을 반복하며 행 자체를 포함하는 객체에 덧붙여 각 행의 색인 반환한다. 제너레이터로 동작하기에, 데이터프레임의 각 행을 하나씩 반환해서 단순 for문에 비해 비교적 효율적으로 연산을 수행할 수 있다. 각 행이 pd.Series 객체로 반환되며 이는 파이썬의 순수 객체가 된다.</p>
<pre><code>%%timeit
# 반복을 통해 행에 적용되는 Haversine 함수
haversine_series = []
for index, row in df.iterrows(): # 행을 하나씩 반환
    d = haversine(40.671, -73.985, row[&#39;latitude&#39;], row[&#39;longitude&#39;])
    haversine_series.append(d)
df[&#39;distance&#39;] = haversine_series

&gt;&gt;&gt; 174 ms ± 22.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)</code></pre><p>위에보다 절반 가까이 줄어들었다.</p>
<ul>
<li>단순 for문은 위도와 경도 모두 각각 행을 찾아가야 된다.</li>
<li>iterrows()를 사용하면 두 작업을 한 번에 진행할 수 있다.<h2 id="3-apply를-사용한-반복">3. apply()를 사용한 반복</h2>
apply()는 iterrows()보다 더 좋은 옵션이라고 할 수 있다. 
apply()는 데이터 프레임의 특정 축(행 또는 열)을 따라 함수를 적용하는데, lambda를 활용하여 haversine 함수를 각 행에 적용해 각 행의 특정 셀을 함수 입력값으로 지정할 수 있다. 축(axis) 옵션을 마지막에 포함하여 행과 열 중 어디에 함수를 적용할지 결정한다.<pre><code>%%timeit
df[&#39;distance&#39;] = df.apply(lambda x : haversine(40.671, -73.985, x[&#39;latitude&#39;], x[&#39;longitude&#39;]), axis = 1)
</code></pre></li>
</ul>
<blockquote>
<blockquote>
<blockquote>
<p>46.1 ms ± 2.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)</p>
</blockquote>
</blockquote>
</blockquote>
<pre><code>apply()는 내부적으로 Cython으로 최적화된 루프를 사용한다.
- Cython : C와 파이썬 사이의 브릿지 역할을 하는 프로그래밍 언어. 파이썬의 동적 타입 시스템을 유지하면서도 C언어의 성능을 얻을 수 있음.  

때문에 본질적으로 행을 반복하지만, iterrows()보다 함수 실행 시간이 반으로 줄어든다.

하지만 결국은 반복문이고, 각 행이 1,631번 이상 실행된다. 아무리 apply()가 날고 긴다해도 1번의 연산으로 문제를 해결하는 vectorization를 이길 수는 없다.
## 4. pd.Series를 사용한 vectorization
판다스의 기본 단위인 데이터 프레임(pd.DataFrame)과 시리즈(pd.Series)는 모두 배열 기반이다. 그리고 벡터화된 연산은 반복문을 사용하지 않고, 배열 전체에 대한 작업을 수행하는 방식을 의미한다.  

판다스는 수학 연산에서 집계부터 문자열 함수에 이르기까지 다양한 벡터화 함수를 포함한다. 판다스의 내장 함수들은 pd.Series와 pd.DataFrame에 최적으로 작동하게끔 되어있다.  

지금까지는 Haversine햄수에 스칼라 값을 전달했다. 하지만 Haversine 함수 내에서 사용하는 모든 함수를 배열 위로 작동시킬 수 있다. 이렇게 하면 Haversine 함수를 매우 간단히 벡터화할 수 있다. 스칼라 값으로 각 위도, 경도를 전달하는 대신 전체 Series(열)을 전달하는 것이다. 이를 통해 판다스는 벡터화 함수에 적용 가능한 모든 최적화 옵션을 활용할 수 있으며, 특히 전체 배열에 대한 모든 계산을 동시에 수행하게 된다.</code></pre><p>%%timeit</p>
<h1 id="각-row를-넣는-대신-벡터-자체를-집어넣는다">각 row를 넣는 대신 벡터 자체를 집어넣는다</h1>
<p>df[&#39;distance&#39;] = haversine(40.671, -73.985, df[&#39;latitude&#39;], df[&#39;longitude&#39;])</p>
<blockquote>
<blockquote>
<blockquote>
<p>1.6 ms ± 68.4 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)</p>
</blockquote>
</blockquote>
</blockquote>
<pre><code>apply()에 비해 거의 30배 가까이 시간이 개선되었다. 입력 유형이 row단위에서 벡터단위로 바뀐 것 뿐, 어떠한 for문과 같은 추가적인 연산이 활용되지 않았다.

당연히 빠를 수 밖에 없는게, apply()는 for문으로 haversine() 함수를 1,631번 이용하는 동안 pandas를 활용한 벡터화는 함수를 1번만 사용했다. 함수를 전체 배열에 대해 동시에 적용하였기 때문이다.
## 5. 넘파이 배열(ndarray)를 사용한 vectorization
Numpy는 수학 및 과학 연산을 위한 파이썬 패키지이며, 내부의 상당부분이 C나 포트란으로 작성되어있어 실행속도가 빠르다. 앞서 살펴본 Pandas의 자료형들도 Numpy의 array자료형(ndarrays)을 활용해 생성 및 수정이 이루어진다. 하지만 index, datatype 확인과 같은 작업으로 인한 오버헤드가 많이 발생하지 않기 때문에, 결과적으로 넘파이 배열에 대한 작업은 판다스 시리즈에 대한 작업보다 빠르다.  

판다스 시리즈가 제공하는 추가 기능을 사용하지 않을 경우, 넘파이 배열을 판다스 시리즈 대신 사용할 수 있다. 예를 들어 Haversine 함수의 벡터화 구현은 실제로 위도, 경도 시리즈의 index를 사용하지 않기 때문에 사용할 수 있는 색인이 없어도 된다. 이에 비해 색인으로 값을 참조해야 하는 데이터 프레임의 조인 같은 작업을 수행한다면 판다스 개체를 계속 사용하는 편이 좋다.

위도와 경도 배열을 pd.Series의 values()를 활용해 pd.Series에서 np.array로 변환하자. np.array를 직접 함수에 전달하면 판다스가 전체 벡터에 함수를 적용시킨다.</code></pre><p>%%timeit</p>
<p>df[&#39;distance&#39;] = haversine(40.671, -73.985, df[&#39;latitude&#39;].values, df[&#39;longitude&#39;].values)</p>
<blockquote>
<blockquote>
<blockquote>
<p>295 μs ± 71.8 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)</p>
</blockquote>
</blockquote>
</blockquote>
<pre><code>처음으로 μs(microsecond)단위가 나왔다.
## 결과
아래 표는 우리의 실험 결과이다.
![](https://velog.velcdn.com/images/_cykim_/post/06e9feda-e3f5-4cf5-bed1-6245e6dea293/image.png)
301밀리초(ms)는 295마이크로초(μs)의 약 1020.34배이다. 즉, μs로 1분정도 걸리는 대용량 데이터에서 같은 작업을 단순 for문을 사용하면 약 17시간이 걸린다. 물론 앞서 numpy vectorization에서 이야기했듯이, 어떤 작업이냐에 따라 최적화를 무작정 사용하는 것은 좋지 않을 수도 있다. 꾸준히 사용함으로써 숙지하여 상황에 따라 가장 적절하게 해당 기법들을 활용할 수 있어야 할 것이다.



** reference
https://blog.naver.com/draco6/221675459372
https://aldente0630.github.io/data-science/2018/08/05/a-beginners-guide-to-optimizing-pandas-code-for-speed.html

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[가설검정 총체적 정리(p-value만 따질게 아니다)]]></title>
            <link>https://velog.io/@_cykim_/%EA%B0%80%EC%84%A4%EA%B2%80%EC%A0%95-%EC%B4%9D%EC%B2%B4%EC%A0%81-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@_cykim_/%EA%B0%80%EC%84%A4%EA%B2%80%EC%A0%95-%EC%B4%9D%EC%B2%B4%EC%A0%81-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 27 Jun 2024 08:00:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>가설검정을 전체적으로 정리해보자</p>
</blockquote>
<p>통계적 가설검정(Stastical hypothesis)은 항상 오류를 수반한다. 참/거짓을 100% 논할 수 없기 때문에 가설의 기각/채택 여부를 확률적으로 접근할 수 밖에 없다. 일반적으로 가설검정을 위한 실험 설계 시 <strong>귀무가설</strong>과 <strong>대립가설</strong>을 둔다.</p>
<p>H0 : negative(부정) - null hypothesis(귀무가설, 영가설)
H1 : assertion(주장) - alternative hypothesis(대립가설)</p>
<p>통계적 가설검정은 기존의 통념이 깨질 수 있는 새로운 가설을 제시하고자 할 때, 기존의 통념을 부정함으로써 새로운 가설을 주장하는 귀납적 접근법이라고 생각해도 될 듯 하다.</p>
<p>가설 검정 실험 자체는 굉장히 쉽다. R이나 Python으로 코트 한 줄이면 충분하다. 하지만 어떻게 실험을 설계하느냐, 어떻게 해석하느냐에 따라 그 결과가 천차만별로 달라질 수 있으므로 보다 개념을 확실히 알아야 할 필요가 있다.(그냥 돌려서 p값이 0.05보다 낮다고 다 통과시키다가 큰일날 수 있다는 소리이다)</p>
<h2 id="정리">&lt;정리&gt;</h2>
<h3 id="1-p-value">1. p-value</h3>
<p>p-value : 귀무가설 하에, 관측된 검정 통계량을 기각시킬 때 발생하는 제 1종 오류의 최솟값(=귀무가설 하에, 극단적인 데이터가 우연히 발생할 확률)
$cdf$ : 기준 분포의 cdf. t-test에서는 정규분포의 cdf가 될 것이다.
$ts$ : 검정 통계량</p>
<ul>
<li>좌측검정에서 p-value = $cdf(ts)$</li>
<li>우측검정에서 p-value = $1-cdf(ts)$</li>
<li>양측검정에서 p-value는 검정 통계량 값이 음수이냐 양수이냐에 따라 각각 &quot;좌측검정의 p-value x 2&quot;, &quot;우측검정의 p-value x 2&quot;가 된다.</li>
</ul>
<h3 id="2-각종-오류들">2. 각종 오류들</h3>
<p>1종 오류(Type I Error) : 귀무가설이 참일 때, 이를 기각할 확률</p>
<ul>
<li>좀 더 쉽게 생각해보자. 기존의 주장이 맞아서 무언가 조치를 취하지 않는 것이 좋은데 자꾸 새로운 주장이 맞다고 때쓰는 것이다.</li>
<li>유의수준(Significance Level, α) : 1종 오류를 허용할 최대 확률. 즉, 사전에 연구자가 미리 정하는 값</li>
<li><blockquote>
<p>유의수준 α를 0.05로 설정한다는 것은 <strong>귀무가설이 참인데도 불구하고 이를 기각하는 오류를 범할 확률을 0.05로 제한한다</strong>는 의미이다. 이 상황에서 p-value가 통제된 1종 오류 값(=유의수준)보다 작은 경우 귀무가설을 신뢰할 수 없게 되는 것이다.</p>
</blockquote>
</li>
<li>신뢰수준(Confidence Level, 1-α) : 파라미터에 대한 신뢰구간이 실제 모집단의 값과 얼마나 근접한지는 나타내는 확률</li>
</ul>
<p>2종 오류(Type II Error) : 대립가설이 참일 때, 귀무가설을 채택할 확률</p>
<ul>
<li>이를 쉽게 생각해보면, 실제로는 새로운 주장이 맞아서 새롭게 조치를 취하는 것이 좋은데 자꾸 기존의 주장이 맞다고 우기는 것이다.</li>
<li>베타(β) : 2종 오류를 범할 확률. 연구자가 직접 설정하지는 않음.</li>
<li><blockquote>
<p>β는 간접적으로 통제될 수 있다. 이는 검정력(Power)과 연결된다.</p>
</blockquote>
</li>
<li>검정력(Power) : 대립가설이 참일 때, 이를 사실로써 결정할 확률</li>
</ul>
<h3 id="3-1종-오류와-2종-오류가-trade-off인-이유가-뭘까">3. 1종 오류와 2종 오류가 trade-off인 이유가 뭘까?</h3>
<p>굉장히 헷갈릴 수 있지만 그림을 참고하여 잘 따라오면 생각보다 쉽게 이해될 것이다.
<img src="https://velog.velcdn.com/images/_cykim_/post/98f7eb69-7366-4a3a-8463-69830d0706cc/image.png" alt="">
일반적으로 가설검정 시 귀무가설을 전제로 하기에 왼쪽 분포를 기준으로 진행한다. 이 때 임계치는 사전에 정하는 유의수준(연구자가 통제하는 1종 오류)에 대한 값이 된다. 실제 분포인 오른쪽 분포는 우리가 알 수는 없지만 분명 존재하며, 정해져있는 값일 것이다. 이 때 그래프 상으로 임계치를 극단으로 옮긴다면 1종오류는 감소하고, 2종오류는 증가하게 된다. </p>
<p>이를 좀 더 풀어서 생각하면 <strong>&quot;검정의 임계값을 더욱 극단적인 값으로 설정하게 되어 1종오류를 범할 확률이 낮아지지만, 귀무가설을 기각하기 어려워지기 때문에(즉, 귀무가설을 채택할 확률이 높아지기에) &#39;대립가설이 참일 경우&#39;에 고려되는 2종오류를 범할 확률이 높아진다.&quot;</strong>라고 볼 수 있다. 대립가설이 참임에도 귀무가설을 채택할 확률인 β가 증가하는 것이다. </p>
<p>β의 증가는 검정력(power, 1-β)의 감소로 이어진다. 따라서 1종오류를 줄이기 위해 유의수준을 더욱 낮출 경우 <strong>대립가설이 참일 때, 이를 사실로써 결정할 확률</strong>이 줄어들게 된다.</p>
<p>상황에 따라 1종오류가 중요한 경우도 있고, 2종오류가 중요한 경우가 있다. 그럴 때마다 이 둘의 trade-off 관계를 잘 고려해서 실험을 설계해야 할 것이다.</p>
<p>예시로 의료계에서는 1종오류를 일으킬 확률을 매우 낮게 주는 경우가 많다. 연구자가 내린 결론이 잘못되었을 가능성을 매우 줄이기 위해서이다. 여기서 연구자가 내린 결론은 대립가설(H1)일 것이고, 1종오류를 일으킬 확률을 낮게 한다는 것은 귀무가설(H0)이 맞는데 잘못 기각할 확률을 낮추는 것이기에 대립가설(H1)을 잘못 채택할 확률을 낮춘다와 같은 의미가 된다.</p>
<h3 id="4-1종오류를-통제한-상황에서-검정력을-높이는-방법은-뭘까">4. 1종오류를 통제한 상황에서 검정력을 높이는 방법은 뭘까?</h3>
<p>2종오류(β)는 검정력(Power, 1-β)과 정확히 반비례 관계이다. 따라서 1종오류가 증가하면, 즉 신뢰수준이 나빠진다면 검정력은 좋아진다. 좀 더 풀어서 생각해보면, 신뢰수준이 낮아지면 유의수준은 올라가고 귀무가설을 기각하기가 쉬워지기 때문에 검정력이 높아지는 것이다.</p>
<p>그러나 2종오류를 줄이기 위해(=검정력을 높이기 위해) 무작정 1종오류를 늘리는 행위는 좋지 않다.</p>
<p>검정력과 연관이 있는 다른 지표들이 있을까?</p>
<ol>
<li>효과 크기(Effect Size)가 높으면 검정력(Power)이 올라간다.</li>
</ol>
<ul>
<li>효과 크기는 두 그룹 간의 차이 또는 연구에서 발견된 효과의 크기를 의미한다. 이는 평균차이가 될 수도, 상관계수가 될 수도 있으며, Cohen&#39;s d와 같이 차이를 표준화한 지표를 활용할 수도 있다.</li>
<li>효과 크기가 클 수록 검정력이 높아지는 이유는 효과 크기가 크게 나타날 경우 통계적 검정에서 이를 발견하기가 더 쉽기 때문이다. </li>
</ul>
<ol start="2">
<li>표본 크기(n)을 높이면 검정력(Power)이 올라간다.</li>
</ol>
<ul>
<li>표본 크기를 높이면 표본 평균의 변동성이 줄어들고, 실제 효과를 더 명확히 감지할 수 있게 된다. 표준 오차가 줄어들기에 검정 통계량의 값이 커지게 되고, 이는 귀무가설을 기각할 가능성이 높아진다. 이는 실제로 대립가설이 참일 때, 이를 발견하고 귀무가설을 기각할 확률이 높다는 의미이다. 즉, 검정력이 높아지는 것과 같은 맥락이다.</li>
</ul>
<h3 id="5-표본-크기를-늘릴-때-주의할-점">5. 표본 크기를 늘릴 때 주의할 점</h3>
<p>물론 실험 조건 등을 개선해서 더 큰 효과를 꽤할 수 있지만, 이를 고려하지 않는 경우 유의수준(α)를 고정한다는 가정하에 표본 크기를 늘림으로써 검정력(Power)를 높일 수 있다. 이는 또한 모집단에 가까워지기에 결과의 신뢰성을 높여줄 것이다.</p>
<p>하지만 표본 크기(n)은 데이터를 얻기 전 미리 설계해두는 것이 좋을 수 있다. 실험 결과를 바탕으로 원하는 결과가 안나왔다는 이유로 n을 늘리면 p-hacking으로 이어질 수 있기 때문이다.</p>
<p><strong>표본 크기(n)이 높아질수록 p-value는 낮아진다. 그 이유는 표본 크기(n)의 증가는 표본 평균의 표준 오차($s/√n$)를 감소시키고, 결과적으로 검정 통계량을 증가시킬 수 있기 때문이다. 이렇듯 n이 높아지면 통계적 유의성을 확보하기는 쉬울 것이다. 그렇다면 n이 정말 높을 때 귀무가설을 기각하면 의미가 있다고 할 수 있을까?</strong></p>
<p>사전에 표본 크기(n)을 따로 설정하지 않고 가지고 있는 데이터를 그대로 사용할 경우 n의 크기가 크다면 귀무가설을 기각해도 과연 의미가 있는 결과인지 의심해봐야 한다. 이 때 효과 크기(Effect size)를 통해 그 효과 혹은 차이가 의미가 있는지 확인할 수 있다. 즉, p-value(통계적 유의성)과 효과 크기(Effect size, 실질적 중요성)을 동시에 따져야 한다.</p>
<p>또한, 사전에 적절한 표본 크기(n)를 설정하는 방법도 있다. 적절한 표본 크기(n)은 α, 1−β, 효과 크기(delta)를 통해 얻을 수 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/9eb3660a-1bbe-4476-b967-60a257947ea5/image.png" alt="">
즉, 유의수준과 검정력, 효과 크기의 범위를 설정하면 n을 구할 수 있고, 이를 바탕으로 가설 검정을 진행할 수 있는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Labelme 설치]]></title>
            <link>https://velog.io/@_cykim_/Labelme-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@_cykim_/Labelme-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Tue, 14 May 2024 04:48:20 GMT</pubDate>
            <description><![CDATA[<p>회사에서 OCR 모델을 구축하게 되었다. PaddleOCR의 text recognizer를 fine-tuning하기 위해 text detection과정을 거치는 도중, annotation 방법을 공유하고자 포스팅하게 되었다.</p>
<p>처음에는 roboflow를 활용했지만,,, annotation을 마무리한 후 데이터를 generate한 결과 화질이 깨져있었다. 어떤 글자인지 알아볼 수 없을 정도로 깨져서 labelme를 활용해 다시 annotation을 진행하였다. 나중에 annotation할 일이 있을 때 보다 수월하게 labelme를 사용할 수 있도록 하기 위해 기록을 남긴다.</p>
<h2 id="labelme-설치">labelme 설치</h2>
<p><a href="https://github.com/labelmeai/labelme#windows">https://github.com/labelmeai/labelme#windows</a>
원래 labelme 설치는 간단하다. 그냥 가상서버 구축 후, pip install labelme로 설치하면 된다.</p>
<p>하지만,,, 계속 오류가 났다.
<img src="https://velog.velcdn.com/images/_cykim_/post/a3ee9d96-38b9-4c5d-a1d8-0f3a95bed152/image.png" alt="">기본적으로 encoding이 cp949로 세팅되어있어서 발생한 오류인 것 같다. 이를 해결하기 위해서는 setup.py파일에서 encoding을 utf-8로 바꾸어 주어야 한다.</p>
<p>번거롭지만 git clone으로 setup.py를 포함한 파일들을 다운받는다.</p>
<pre><code>git clone https://github.com/labelmeai/labelme.git</code></pre><p><img src="https://velog.velcdn.com/images/_cykim_/post/f3f09869-fa28-4474-8481-5833f07c889d/image.png" alt="">
받은 labelme 폴더 내 setup.py에 들어가서 오류 부분을 찾는다.
<img src="https://velog.velcdn.com/images/_cykim_/post/51cd76c8-bf49-4f21-b93d-2f01d914d8f7/image.png" alt="">기존에 없었던 &#39;encoding = &#39;utf-8&#39;을 추가해준 모습이다.</p>
<p>이후 shell(anaconda prompt)에서 labelme폴더로 경로설정을 한 후 pip install .을 치면 수동으로 다운받을 수 있다.</p>
<pre><code>C:\Users\cykim\Desktop\labelme&gt; pip install .</code></pre><p>설치가 끝난 후 shell에 labelme를 입력하면 창이 뜬다. 어떤 경로에서 실행하던 상관없다.</p>
<pre><code>C:\Users\cykim\Desktop\labelme&gt; labelme</code></pre><p><img src="https://velog.velcdn.com/images/_cykim_/post/bb9d3134-0393-4032-aaa1-3ad178c38e68/image.png" alt="">위와 같은 창이 뜬다면 성공적으로 labelme를 실행한 것이다.</p>
<p>사용 방법은 해당 <a href="https://m.blog.naver.com/yh_park02/222315567498">블로그</a>에서 자세히 확인할 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mutable과 Immutable, 알아야 하는 이유]]></title>
            <link>https://velog.io/@_cykim_/Mutable%EA%B3%BC-Immutable%EC%9E%91%EC%84%B1%EC%A4%91</link>
            <guid>https://velog.io/@_cykim_/Mutable%EA%B3%BC-Immutable%EC%9E%91%EC%84%B1%EC%A4%91</guid>
            <pubDate>Fri, 19 Apr 2024 01:21:19 GMT</pubDate>
            <description><![CDATA[<p>파이썬의 객체는 크게 두 가지 종류로 나눌 수 있다.
mutable(값이 변하는 객체)와 immutable(값이 변하지 않는 객체)이다. 이를테면 list, dict 등은 특정 값을 넣고 빼고 할 수 있으므로 mutable하다고 할 수 있지만, str이나 tuple과 같은 경우 수정 자체가 불가능하기 때문에 immutable한 객체라고 부를 수 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/2c03b243-1761-45ee-854e-be2c77021331/image.png" alt="">
<strong>mutable과 immutable을 이해하면 객체의 복사에 대한 개념이나 또는 함수에서 변수가 매개변수로써 전달될 때 기존의 입력 변수값이 변경되는지 등을 파악하는 데 충분히 도움이 된다.</strong> 따라서 코딩해보면서 익숙해지는 것이 좋다.</p>
<p>실험 코드를 작성해보겠다.</p>
<pre><code># mutable 예시

a = [&#39;a&#39;]
c = [&#39;a&#39;]

print(id(a))
print(id(c))

&gt;&gt;2646997146176
&gt;&gt;2646997141952</code></pre><p>list의 경우 mutable한 객체이다. 같은 원소의 리스트라도 할당될 때마다 다른 주소값을 가진다.</p>
<pre><code># immutable 예시

a = &#39;a&#39;
c = &#39;a&#39;

print(id(a))
print(id(c))

&gt;&gt;140720624684256
&gt;&gt;140720624684256</code></pre><p>str은 immutable한 객체이다. 이 경우 같은 값을 할당했는데 list와 다르게 같은 주소값을 가진다.</p>
<p>우리는 이 실험에서 &#39;a&#39;라는 문자열은 특정 메모리에 고정적으로 할당된 것이라고 생각할 수 있다. 반면, list는 실행할 때마다 메모리에 추가적으로 할당되는 것이다.</p>
<p>python의 list가 linked list인 것을 생각하면 충분히 이해가 가는 것이, array list 구조가 아니기 때문에 위치가 고정되지 않는 것이다.</p>
<p>참조와 복사에 대해서도 재미있는 실험을 할 수 있었다. 이에 대해서 <a href="https://velog.io/@_cykim_/%EC%B0%B8%EC%A1%B0%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC">포스팅</a>해두었다.</p>
<pre><code>a = &#39;a&#39;
c = a

print(id(a))
print(id(c))

&gt;&gt;140720624684256
&gt;&gt;140720624684256</code></pre><p>단순히 참조 복사만 진행한다면 c는 &#39;a&#39;의 메모리 주소를 참조하기에 같은 a와 c는 동일하게 &#39;a&#39;가 속해있는 메모리 주소 id를 가진다. 하지만 얕은 복사인 .copy()는 지원되지 않는다.</p>
<pre><code>---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[58], line 4
      1 # list는 mutable
      3 a = &#39;a&#39;
----&gt; 4 c = a.copy()
      6 print(id(a))
      7 print(id(c))

AttributeError: &#39;str&#39; object has no attribute &#39;copy&#39;</code></pre><p>이렇듯 &#39;copy()&#39; 메소드는 mutable 객체의 얕은 복사를 지원하는 데에만 사용된다. 이유를 생각해보자. python에서 immutable한 객체는 얕은 복사나 깊은 복사의 필요성이 없는데, 한번 생성되면 그 상태를 변경할 수 없기 때문이다. 
반면, mutable한 객체는 복사본을 만들 시 단순히 참조하는 것만으로는 충분하지 않다. 하나의 변수를 통해 객체를 수정하면 다른 모든 참조도 그 변경을 반영하기 때문에 사용자가 원하지 않는 상황이 발생할 수 있다(사실 필자도 이런 부분에서 해당 개념에 대해 공부의 필요성을 느꼈다).</p>
<p>결론적으로 copy() 메소드는 mutable 객체의 얕은 복사(shallow copy)를 만들어주며, 이는 원본 객체의 최상위 레벨만 복사하는 것이므로, 내부에 또 다른 객체가 있을 경우 그 객체만 참조한다.</p>
<pre><code>a = [&#39;a&#39;]
c = a.copy()

print(id(a)) # [&#39;a&#39;]
print(id(c)) # 얕은 복사
print()
print(id(a[0])) # &#39;a&#39;
print(id(c[0])) # 얕은 복사 내 객체

# list의 id는 다름. 즉, 새로운 memory에 생성된 것.
&gt;&gt;2646997613568
&gt;&gt;2646997684736

# list 내 객체는 참조
&gt;&gt;140720624684256
&gt;&gt;140720624684256</code></pre><p>Reference
<a href="https://wikidocs.net/91520">https://wikidocs.net/91520</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[참조(Reference)와 얕은 복사(Shallow copy), 깊은 복사(Deep copy)]]></title>
            <link>https://velog.io/@_cykim_/%EC%B0%B8%EC%A1%B0%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC</link>
            <guid>https://velog.io/@_cykim_/%EC%B0%B8%EC%A1%B0%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC</guid>
            <pubDate>Thu, 18 Apr 2024 00:59:58 GMT</pubDate>
            <description><![CDATA[<p>데이터 전처리, 분석, 모델링 등등 내가 하는 모든 업무에 파이썬이 사용된다. 하지만 종종 &quot;왜 이렇게 뜨지?&quot;라며 의문이 들 때가 있었고, 코드 상의 문제도 있지만 많은 경우 파이썬 자체적인 이해가 필요한 부분이기도 했다. 그 중 하나가 참조의 영역이다.</p>
<p>참조는 파이썬을 처음 배울 때 정말 많이 접하는 단어이기도 하지만, 대개 &#39;그냥 복제되는 거네&#39;하고 넘어가는 경우가 대부분이다. 하지만 참조와 복사는 분명히 다른 개념이며, 간단히 짚고 넘어가고자 한다.</p>
<h1 id="참조reference">참조(Reference)</h1>
<p> 참조(Reference)는 특정 데이터를 가리키거나 &#39;참조&#39;하는 것을 말한다. 참조를 통해 데이터에 접근하고 수정할 수 있으며, 참조를 통한 수정은 원본 데이터에 직접 반영된다.
 C언어에서의 포인터라고 생각하면 된다. 예를 들어 b = a 라고 했을 때, b는 a가 가리키고 있는 객체와 같은 객체를 가리키게 된다. 이후 b를 통해 객체를 변경하면 a를 통해 참조하는 객체도 변경된다. 즉, 하나의 객체에 두 개의 이름(참조)이 있는 것이다.</p>
<pre><code>a = [1,2,3]
b = a # 참조 관계 형성
b[0] = 10 # b는 a와 동일한 리스트를 참조하고 있기 때문에, 이 변경은 a에도 영향을 미침
print(a)
&gt;&gt;&gt; [10, 2, 3]</code></pre><p>그럼 아래의 경우는 어떨까?</p>
<pre><code>a = [1,2,3]
b = a
b = b + [10]
print(a)
&gt;&gt;&gt; [1,2,3]</code></pre><p>분명 참조의 개념을 사용하면 [1,2,3,10]이어야 한다. 하지만 b는 [1,2,3,10]이 되었을지언정 a는 [1,2,3] 그대로 유지되고 있다. 여기서 &#39;+&#39; 연산자의 역할을 파악해야 되는데, <strong>&#39;+&#39; 연산자로 인해 b에 새로운 리스트를 할당하게 되면서 a와 b가 서로 다른 객체를 참조하게 되는 것이다.</strong></p>
<h1 id="참조와-복사">참조와 복사</h1>
<p>참조(Reference)는 특정 데이터를 가리키거나 &#39;참조&#39;하는 것이라고 했다. 반면, 복사(Copy)는 데이터의 독립적인 복제본을 만드는 것이다. 즉, 원본 데이터를 새로운 위치에 완전히 새로운 복제본을 만들어내는 것으로 생각하면 된다. 복사본은 원본과 동일한 값을 가지지만 서로 다른 메모리 주소를 가리키기 때문에, 복사본을 수정해도 원본에는 영향을 미치지 않는다.</p>
<pre><code># 복사의 예
original_list = [1, 2, 3]
copied_list = original_list.copy()  # original_list의 복사본을 만듦
copied_list[0] = 10  # 복사본을 수정
print(original_list)  # [1, 2, 3]
print(copied_list)    # [10, 2, 3]

# 참조의 예
original_list = [1, 2, 3]
referenced_list = original_list  # original_list를 참조
referenced_list[0] = 10  # 참조된 리스트를 수정
print(original_list)  # [10, 2, 3]
print(referenced_list)  # [10, 2, 3]
</code></pre><h2 id="얕은-복사와-깊은-복사">얕은 복사와 깊은 복사</h2>
<p>복사(Copy)는 얕은 복사(Shallow copy)와 깊은 복사(Deep copy)로 나눌 수 있다.</p>
<ul>
<li>얕은 복사(Shallow copy) : 새 객체를 생성하지만, 그 내용은 원본 객체에 저장된 데이터의 참조로 채워진다. 즉, 복사된 객체의 내부 항목들은 여전히 같은 메모리 주소를 참조하고 있는 원본 객체의 항목들인 것이다. 따라서 얕은 복사를 수행한 후, 내부 객체 중 하나를 변경하면, 해당 변경 사항이 원본과 복사본 양쪽에 영향을 미친다.</li>
<li>깊은 복사(Deep Copy) : 새 객체를 생성하고, 원본 객체에 저장된 데이터의 &quot;완전한 복사본&quot;으로 채워진다. 이는 원본 객체의 내부 항목들이 가진 실제 값들을 새로운 메모리 위치에 복제하는 것을 의미한다.<pre><code>import copy
</code></pre></li>
</ul>
<h1 id="얕은-복사-예시">얕은 복사 예시</h1>
<p>original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
shallow[0][0] = &#39;changed&#39;</p>
<h1 id="original00도-changed로-바뀜">original[0][0]도 &#39;changed&#39;로 바뀜</h1>
<h1 id="깊은-복사-예시">깊은 복사 예시</h1>
<p>original = [[1, 2, 3], [4, 5, 6]]
deep = copy.deepcopy(original)
deep[0][0] = &#39;changed&#39;</p>
<h1 id="original00은-바뀌지-않음">original[0][0]은 바뀌지 않음</h1>
<pre><code>이렇게 보면 참조와 얕은 복사가 매우 비슷해보인다. 참조와 얕은 복사를 비교하면서 포스팅을 마무리하도록 하겠다.
## 참조와 얕은 복사...?
참조(Reference)
- 객체에 대한 직접적인 접근 방법이나 그 객체의 주소를 가지고 있음
- 객체를 참조하는 변수들은 모두 동일한 객체를 가리킴
- 하나의 변수를 통해 객체가 변경되면, 같은 객체를 참조하는 다른 모든 변수에 그 변경 사항이 반영됨</code></pre><p>a = [1, 2, 3]
b = a  # b는 a가 가리키는 리스트를 참조함.
b[0] = 10  # a와 b는 동일한 리스트를 가리키므로 a[0]도 10으로 변경됨.</p>
<pre><code>
앝은 복사(Shallow Copy)
- 최상위 컨테이너는 새로운 객체가 생성되지만, 컨테이너 내부의 객체들(내부 요소)은 원본 객체의 참조를 공유함
- 컨테이너의 내부 요소가 변경되지 않는 이상, 원본과 복사본은 서로 독립적임
- 내부 요소가 변경가능한 객체인 경우, 그 내부 요소를 변경하면 원본과 복사본 모두에 영향을 줌</code></pre><p>a = [1, 2, [3, 4]]
b = a[:]  # 얕은 복사를 통해 a의 최상위 리스트를 새로운 객체로 복사함.
b[2][0] = 10  # 내부 리스트 [3, 4]는 복사되지 않고 참조가 공유되므로 a[2][0]도 10으로 변경됨.</p>
<p>```
결론적으로, 참조는 같은 객체에 대한 다른 경로를 제공하는 반면, 얕은 복사는 새로운 컨테이너 객체를 만들지만 그 내부 요소에 대한 참조는 원본 객체와 공유한다고 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Knowledge Distillation에 대한 고찰(feat. Cross Entropy)]]></title>
            <link>https://velog.io/@_cykim_/Knowledge-Distillation%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@_cykim_/Knowledge-Distillation%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Fri, 12 Apr 2024 01:49:03 GMT</pubDate>
            <description><![CDATA[<p><a href="https://arxiv.org/abs/1503.02531">https://arxiv.org/abs/1503.02531</a></p>
<p>Knowledge Distillation(지식증류)를 활용해서 모델 경량화를 진행하던 도중 한 가지 의문이 발생했고 한번 수식을 파고 들어봤다. 까먹기전에 블로그에 올려놔야겠다.</p>
<p><img src="https://velog.velcdn.com/images/_cykim_/post/c4c50173-3ad8-48fa-8138-8d0f89519a16/image.png" alt=""></p>
<p>Knowledge Distillation의 수식을 살펴보면 KL Divergence와 CrossEntropyLoss로 구성되어있다. 하지만 <strong>CrossEntropy 또한 “예측분포와 실제분포 간의 손실”을 계산하는 것인데, 굳이 KL Divergence를 사용해야 하는 이유가 있을까? 라는 생각이 들었다.</strong> 이를 고찰하기 위해 먼저 KL Divergence와 CrossEntropy의 차이부터 알아보자.
<img src="https://velog.velcdn.com/images/_cykim_/post/73d30a56-b227-41e1-8c9b-fef341813f9e/image.png" alt="">
즉, Cross Entropy Loss는 KL Divergence와 실제 분포의 엔트로피 합으로도 표현될 수 있다.</p>
<p>$$Cross Entropy Loss = KL divergence + Entropy(P)$$</p>
<p>그럼 이와 같이 해석할 수 있다.</p>
<p><strong>“KL Divergence 자체는 실제 분포 P의 엔트로피를 직접적으로 계산하지 않고, $$Q$$가 $$P$$를 얼마나 잘 대체할 수 있는지만을 평가한다”</strong></p>
<p>실제 분포 $$P$$의 엔트로피를 직접적으로 계산한다는 것은 해당 분포의 내재된 불확실성까지 고려한다는 것을 의미한다.</p>
<p>이를 Knowledge Distillation에 적용해보자</p>
<blockquote>
<p>“Knowledge Distillation의 첫 번째 항인 KL Divergence는 Teacher Model이 가지는 Softmax 분포의 엔트로피(평균 정보량)을 직접적으로 계산하지 않는다. 즉, Teacher Model의 내재된 불확실성까지 고려하는 것이 아닌, 그저 Student Model과의 분포 간 차이를 고려할 뿐이다. 반면, 두 번째 항인 Cross Entropy Loss는 기존의 ‘실제 분포와 예측 분포 간 차이’를 ‘실제 분포의 내재된 불확실성’까지 고려하는 것이다.&quot;</p>
</blockquote>
<p>하지만 재미있는 부분은 Teacher과 Student를 비교하는 항에서 KL Divergence 대신 Cross Entropy Loss를 사용해도 동일하게 Knowledge Distillation이 작동된다는 것이다. 모델 학습 자체가 Student의 매개변수에 대해서만 편미분되기 때문이다. Teacher Model의 분포는 고정되어 있기에 $$H(T,S) = H(T) + D_{KL}(T||S)$$이며, 해당 손실함수 항에서 Student의 매개변수에 대해 편미분하면 $$H(T)$$는 상수처리가 되면서 소거된다.</p>
<p>즉, <strong>Knowledge Distillation 내 실제로 지식이 증류되는 항에서는 KL Divergence를 사용하던 Cross Entropy Loss를 사용하던 결과는 같다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Logit in Deep Neural Network]]></title>
            <link>https://velog.io/@_cykim_/Logit-in-Deep-Neural-Network</link>
            <guid>https://velog.io/@_cykim_/Logit-in-Deep-Neural-Network</guid>
            <pubDate>Thu, 04 Apr 2024 07:00:20 GMT</pubDate>
            <description><![CDATA[<p>딥러닝 모델의 출력값은 로짓(logit)이라고 불리는 값으로 결과를 내놓는다. 여기서 로짓값은 모델의 마지막 선형 레이어에서 나온 예측값으로, 활성화함수를 거치지 전의 값이라고 생각할 수 있다. 보통 해당 로짓값에 소프트맥스(softmax) 함수를 사용해서 크로스 엔트로피(Cross Entropy)를 구하곤 한다.</p>
<p>여지껏 그냥 자연스럽게 해당 개념을 인지하고 모델링을 해오곤 했는데, 문득 class model()을 구현 할 때 마지막 x값이 그냥 linear 결괏값이지 않나? 왜 로짓값이지? 로짓함수를 씌우지 않는데? 라는 궁금증이 생겼고, <strong>통계학에서 로지스틱과 딥러닝간에 &#39;애매한&#39; 연결고리가 아닌 &#39;확실한&#39; 연결고리를 찾고 싶었다.
**
**&quot;둘다 로짓함수 쓰잖아&quot; 라는 말로 뭉뚱거리고 싶지 않아진 것이다.</strong></p>
<h2 id="통계학에서의-로짓">통계학에서의 로짓</h2>
<p>일반적으로 통계학에서 로짓값은 확률을 odds로 변환한 후, 이 odds에 자연로그를 취한 값이다.
<img src="https://velog.velcdn.com/images/_cykim_/post/efa96604-5ce7-4e5d-b6ea-89d17e86ab1d/image.png" alt=""></p>
<ul>
<li>p : 성공 확률, 0 &lt; p &lt; 1</li>
<li>p/(1-p) : odds</li>
</ul>
<p>그리고 해당 로짓값을 타겟으로 하여 회귀분석을 하면 로지스틱 회귀분석이 된다. 이로부터 확률값을 예측할 수 있으며, 그 확률값을 로짓 함수의 역함수인 로지스틱 함수(=시그모이드 함수)에 반영하여 0과 1의 예측값을 산출하는 것이다.</p>
<h2 id="딥러닝에서의-로짓">딥러닝에서의 로짓?</h2>
<p>딥러닝에서 각 layer는 선형회귀의 연속으로 이해할 수 있다. 여러 개의 linear layer을 겹겹이 쌓고 각 layer마다 activation function을 추가함으로써 non-linearity를 부여하는 것이다. 아래 예시코드는 예전에 내가 한번 짜본건데, 보통 def forward(self,x)의 결괎값으로 x를 리턴한 후, 이를 softmax에 씌워 crossentropyloss를 구한다.
(torch.nn.crossentropyloss() 내부적으로 softmax()가 내장되어 있기에 원시 x값을 그대로 리턴하면 torch.nn.crossentropyloss()가 알아서 계산해준다. 만약 굳이 softmax(x)로 리턴하고자 한다면 torch.nn.NLLLoss()를 사용하는 방법도 있다.)</p>
<pre><code>class Net(nn.Module):
    def __init__(self):
        super().__init__()
        # 입력 이미지 크기: 3x200x200
        self.conv1 = nn.Conv2d(3, 6, 5) # Output: (6, 196, 196) 
        self.pool = nn.MaxPool2d(2, 2)  # Output: (6, 98, 98)
        self.conv2 = nn.Conv2d(6, 16, 5) # Output: (16, 94, 94)
        # 다시 pooling 적용: (16, 47, 47)
        self.conv3 = nn.Conv2d(16, 32, 5) # Output: (32, 43, 43)
        # 다시 pooling 적용: (32, 21, 21)

        # fully connected 레이어의 입력 크기를 계산
        # 3번의 maxpool 레이어를 거치고 나면 크기가 200 -&gt; 100 -&gt; 50 -&gt; 25
        self.fc1 = nn.Linear(32 * 21 * 21, 120)  # 32 * 21 * 21 from last conv layer
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 2)  # 최종 클래스는 2개

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x))) # Output: (6, 98, 98)
        x = self.pool(F.relu(self.conv2(x))) # Output: (16, 47, 47)
        x = self.pool(F.relu(self.conv3(x))) # Output: (32, 21, 21)
        x = x.view(-1, 32 * 21 * 21)  # flatten all dimensions except the batch
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)  # 마지막 레이어에서는 softmax 또는 log-softmax를 사용합니다 (CrossEntropyLoss 사용 시 생략 가능)
        return x</code></pre><p>결국 리턴되는 output도 각 layer의 결괏값과 같이 회귀식의 결과로써 해석할 수 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/f7f01d16-1ead-401a-b419-8a2d2b375f0b/image.png" alt=""></p>
<p>그럼 non-linearity를 부여하기 전의 값은 그저 선형회귀의 결괏값이 아닌가? 맞는 말이다. 다만, 이를 로짓값으로 보는 것은 특별히 로짓함수를 사용해서 도출하는 것이 아니라, <strong>출력값을 로짓값으로써 해석하자!</strong>라고 생각하는 것이 편하다.</p>
<p><strong>결국 관점의 문제였다.</strong></p>
<p>출력값을 로짓값으로 바라보게 되면 결국 로지스틱 분석과 마찬가지로 모델 출력을 확률적으로 해석할 수 있다. 해당 값을 로지스틱 함수(로짓함수의 역함수 = 시그모이드 함수 = 이진분류에서 소프트맥스 함수)에 적용함으로써 0~1 사이의 확률값으로 변환가능하기 때문이다.</p>
<p>딥러닝을 계속 다루다보면 기본적인 부분에서 계속 사소한 궁금증이 발생하게 된다. 그리고 통계적으로 연결지음으로써 기초통계 ~ 딥러닝까지의 관계가 그려지는 것에 재미를 느낀다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Paper Review] ADAM: A METHOD FOR STOCHASTIC OPTIMIZATION]]></title>
            <link>https://velog.io/@_cykim_/Paper-Review-ADAM-A-METHOD-FOR-STOCHASTIC-OPTIMIZATION</link>
            <guid>https://velog.io/@_cykim_/Paper-Review-ADAM-A-METHOD-FOR-STOCHASTIC-OPTIMIZATION</guid>
            <pubDate>Sat, 30 Mar 2024 07:07:00 GMT</pubDate>
            <description><![CDATA[<h1 id="논문-소개">논문 소개</h1>
<p><a href="https://arxiv.org/abs/1412.6980">https://arxiv.org/abs/1412.6980</a>
모델링 과정에서 매개변수가 어떻게 학습이 되는지 머릿속으로 그려볼 필요가 있었다. Optimizer의 종류에 따라 손실함수 f의 움직임이 달라진다. 다양한 Optimizer의 중심이라고 할 수 있는 Adam을 자세히 읽어보았다.</p>
<h2 id="introduction">INTRODUCTION</h2>
<p>목적함수에 확률성, 랜덤성이 있음을 가정한 상태에서 AdaGrad, RMSProp으로부터 영감을 받아 Adam을 설계했음을 설명하고 있다. 다른 optimizer처럼 first gradient step을 사용함으로써 메모리 전략을 강조하면서도  first and second moments of the gradients를 통해 adaptive learning을 유도하고 있다.</p>
<p>SGD, AdaGrad, RMSProp을 아래와 같이 간단하게 정리했다. 
<img src="https://velog.velcdn.com/images/_cykim_/post/bd1c3547-3999-42c7-a64b-e7764e219585/image.png" alt=""></p>
<h2 id="algorithm">ALGORITHM</h2>
<p>Adam의 알고리즘은 이와 같다.
<img src="https://velog.velcdn.com/images/_cykim_/post/6dffd3e4-3150-47c0-8333-01833fce298a/image.png" alt="">
알고리즘만 보는건 어질어질하니 ,,, 수식으로 재표현하면서 이해해보자 !
<img src="https://velog.velcdn.com/images/_cykim_/post/fbefa59c-8813-4c8f-ba92-5683faf81844/image.png" alt="">
여기서 중요한 점은 여타 알고리즘처럼 $g$를 그대로 사용하지 않고 $E(g)$를 사용했다는 점과, $E(g)=mt$와 $E(g^2)=vt$를 지수가중평균으로 구성했다는 점, 그리고 bias-corrected estimated를 활용하여 편향을 제거한 점이다. bias-corrected estimated는 논문 뒷쪽에 자세히 설명되어있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/d098e923-4963-4ac1-a556-be7e878edc37/image.png" alt="">
Adam의 update rule에서 엡실론을 0으로 잡았을 때, step을 구성하는 SNR은 두 가지의 상한선이 존재한다고 한다. 이를 1)과 2)로 구분해서 설명하는데, 1)은 step의 상한이 높아지는 불안정한 상황을 의미하게 된다. 일반적인 경우는 2)인데, 이는 $(1-b_1)=(1-b_2)^{1/2}$인 상황이고 $SNR$이 1보다 작거나 같은 안정적인 경우라고 볼 수 있다. 안정적이라고 볼 수 있는 이유는 최적화에서는 $signal$$($신호, 기울기$)$가 클수록 불안정하기 때문이다.
(** 원래 신호처리에서 $SNR$은 값이 클수록, 즉 잡음이 신호에 비해 약할수록 안정적이다. 최적화의 관점에서 바라보아야 하며 저자도 이에 대해 $SNR$이라는 용어가 abused되었음을 명시하고 있다.)</p>
<h2 id="initialization-bias-correction">INITIALIZATION BIAS CORRECTION</h2>
<p>Adam은 initialization bias correction term을 사용한다. 해당 논문에서는 vt(second moments of the gradients)를 기준으로 설명했지만, mt(first moments of the gradients)도 마찬가지의 원리를 가진다. 나는 읽으면서 직관적인 해석과 통계학적인 해석으로 나누어서 살펴보았다.</p>
<ol>
<li><p>직관적 해석
gradiant의 평균 $mt$와 변동성 $vt$는 $t=0$일 때 $0$으로 초기화하게 된다. 이때 $t=1$이 되면 $m1, v1$이 실제 값보다 작아진다. 이는 $(1-b1)$으로 인해 발생하는 문제이며, 해당 수식으로 나눠줌으로써 문제를 해결할 수 있다.
(** 사실 변동성이라는 용어를 사용하면 안된다. 정확히는 uncentered variance, 즉 중심화되지 않은 분산이라고 표현된다. 이는 &quot;중심으로부터 분포의 퍼짐 정도&quot;를 보는 것이 아니라 &quot;확률변수 X에서의 변동 정도의 평균&quot;을 의미하는 것이다. 하지만 의미적으로 유사하기에 편의상 &#39;변동성&#39; 용어를 정리하고자 한다.)
<img src="https://velog.velcdn.com/images/_cykim_/post/6f78e0fb-cc03-4499-862a-6b2119e1f468/image.png" alt=""></p>
</li>
<li><p>통계적 해석
해당 논문에서 $vt$를 기준으로 설명하기에, 정리도 $vt$로 했다. $t$를 $0$부터 하나씩 넣어보면 $vt$를 summation term으로 정리할 수 있고, 분포가 존재한다는 가정 하에 vt에 Expectation을 씌움으로써 $E(g^2)$와의 관계를 확인했다. 아래 정리처럼 $(1-b2^2)$로 나눠주어 $vt$를 불편추정량으로 유도할 수 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/2d3941f1-4615-4b96-8150-673047937827/image.png" alt=""></p>
</li>
</ol>
<h2 id="convergence-analyse">CONVERGENCE ANALYSE</h2>
<p>Adam의 convergence를 분석하는 파트이다. 데이터 포인트 마다의 변화를 관측하기 위해 online learning framework 방식을 가져갔으며, Regret을 정의하고 그 상한을 증명함으로써 Adam의 convergence를 정당화했다.
<img src="https://velog.velcdn.com/images/_cykim_/post/7c8e1e7f-6612-401a-aaf7-f1739535729a/image.png" alt="">
Theorem 4.1를 살펴보면 $R(T)$의 상한을 정의하는 summation term이 설명되어있다. 
<img src="https://velog.velcdn.com/images/_cykim_/post/fcf08a6a-926f-4180-adbf-a4049277340a/image.png" alt="">
data feature가 sparse하고 bounded gradient를 가질수록 기존의 상한보다 Regret이 작아질 수 있음을 설명하고 있다.
(개인적인 생각 -&gt; data의 feature가 sparse하다면 $R(T)$가 낮아지고, 이는 시간에 따른 후회지수의 상한이 낮아질 수 있음을 시사하며 최적값을 향하는 step의 누적 이동거리가 낮아지는 것을 의미하게 된다. 때문에 data feature가 sparse한 경우 learning rate를 낮추는 것이 학습에 유리하지 않을까라는 생각을 했다.)</p>
<p>앞의 가정들이 충족할 때, $T&gt;=1$에 대해서
<img src="https://velog.velcdn.com/images/_cykim_/post/23dfec7d-4ce4-4f2b-b057-0763bbdab16d/image.png" alt="">
가 충족한다. $R(T)/T$는 평균을 의미하고, 이 상태에서 $T$가 무한대로 간다면 평균적으로 후회는 없어짐을 수식적으로 증명하고 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/c80f474b-06ac-4a4f-ba1d-33462291903d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Paper Review] TS-Bert: Time series Anomaly Detection via
Pre-training Model Bert]]></title>
            <link>https://velog.io/@_cykim_/paper-review-TS-Bert-Time-series-Anomaly-Detection-viaPre-training-Model-Bert</link>
            <guid>https://velog.io/@_cykim_/paper-review-TS-Bert-Time-series-Anomaly-Detection-viaPre-training-Model-Bert</guid>
            <pubDate>Sat, 16 Mar 2024 05:06:01 GMT</pubDate>
            <description><![CDATA[<h1 id="논문-소개">논문 소개</h1>
<p><a href="https://www.researchgate.net/publication/352321708_TS-Bert_Time_Series_Anomaly_Detection_via_Pre-training_Model_Bert">https://www.researchgate.net/publication/352321708_TS-Bert_Time_Series_Anomaly_Detection_via_Pre-training_Model_Bert</a></p>
<p>해당 논문은 2021년에 나온 논문으로 트랜스포머 계열 모델을 시계열 문제에 적용하려고 시도한 연구이다. 시계열에 LLM을 접목하여 유의미한 성능을 얻었다는 부분에서 분명 의의가 있다고 생각한다. 전체 내용을 다루기 보다는 중요한 부분만 가져와서 리뷰하도록 하겠다.</p>
<h2 id="introduction">INTRODUCTION</h2>
<p>대규모의 monitoring data를 분석하는 것은 기업에게 매우 중요하며, 때문에 monitoring system에서로부터 생성된 large volume of time series에서 potential error와 anomaly를 분석하는 것은 굉장히 중요하다. 하지만 저자는 기존에 널리 채택된 CNN, RNN, LSTM과 같은 모델들은 long-distance dependency에서 자유로울 수 없기에, self-attention으로 이를 해결한 transformer계열의 Bert모델을 시계열 문제에 적용하고자 했다. TS-Bert는 기존의 Bert와 같이 pre-training(MLM, NSP)과 fine-tuning과정을 거치게 된다. TS-Bert의 강점은 unsupervised model인 것이며, 이것이 가능한 이유는 SR를 활용한 labeling이 가능하기 때문이다.</p>
<p>-&gt; 사실 unsupervised model라고 표현하는 게 맞나 싶다. 엄밀히 따지면 SR로 라벨링하고, 학습 과정에서 TS-Bert가 라벨링된 결과를 학습하기 때문에 supervised model이다. 아마 라벨링 과정 또한 같은 프로세스에 넣음으로써 label이 없는 데이터에서 anomaly detection이 가능하다고 표현한 것 같다.</p>
<p>실험은 KPI dataset과 yahoo dataset으로 진행하였으며, 기존의 SOTA solution에 비해 각각 21%, 11% 높은 정확도를 기록했다고 한다.</p>
<h2 id="methodology">METHODOLOGY</h2>
<p>Time series anomaly detection(TSAD)에서 문제 정의는 다음과 같이 이루어질 수 있다.
<img src="https://velog.velcdn.com/images/_cykim_/post/3bbdaa7c-3515-4fa3-b43a-71da269e48b7/image.png" alt="">
즉, timestamp 만큼의 차원 공간 R^n에 T가 존재하며, 이때 task는 해당 공간에 있는 output vector Y를 생성하는 작업으로 생각할 수 있다.</p>
<p>해당 연구에서는 위 TSAD 문제를 TS-Bert로 다루게 된다. 모델의 robustness를 증가시키기 위해 모든 time series dataset은 정규화된 상태이다. 또한 기존의 Bert는 TSAD task를 해결하기 위해 설계되지 않았으므로 detection accuracy를 높이기 위한 수정 작업을 거치게 된다. 아래 그림은 TS-Bert의 구조이다. 기존의 Bert처럼 Pre-training 과정에서 NSP와 Mask LM 학습을 수행함으로써 양뱡향적 시계열성 정보를 학습하게 되며, Fine-tuning과정에서 SR를 활용한 labeling 작업 후 anomaly detection을 수행하게 된다.
<img src="https://velog.velcdn.com/images/_cykim_/post/2f4d839b-a4df-4504-9fd9-42cfbcdbf037/image.png" alt=""></p>
<h2 id="data-preprocessing">Data Preprocessing</h2>
<p>Data preprocessing은 Min-max scaling을 사용한다. 여기서 scale을 곱하게 된다.
<img src="https://velog.velcdn.com/images/_cykim_/post/26a5ac5f-97d4-4f94-b68e-ac46e81acc2e/image.png" alt="">
scale을 곱하는 이유는 Min-max scaling값 자체는 0~1사이의 실수값이기에, 정수값을 input으로 기대하는 Bert에 맞출 필요가 있기 때문이다.</p>
<blockquote>
<p> Pre-training</p>
</blockquote>
<p>Pre-training과정에서는 기존의 Bert와 마찬가지로 Masked LM과 Next Sentence Prediction을 수행하게 된다. 이 때, input data로 사용되는 time series data에는 anomaly가 없다고 가정한다.
알고리즘 형태는 다음과 같다(아마 4번 줄에서 xi에 tilde가 붙지 않은 것은 오타인 듯 하다...)
<img src="https://velog.velcdn.com/images/_cykim_/post/2af25484-5768-4bb2-bbf2-86285d881749/image.png" alt="">
논문에 Step별로 자세히 소개가 되어있으므로 간략히 느낌만 적자면, scaling된 x를 window size(Li)만큼 자르되 길이가 pw인 subsequence가 여러 개 생성되는 것이고, 각각의 subsequence인 sj가 di에 들어감으로써 subsequence vector의 집합 di가 생성된다. 그 상태에서 Bert에 데이터를 학습시켜 Pre-trained된 Bert model을 얻게 된다.</p>
<h2 id="fine-tuning">Fine-tuning</h2>
<p>Fine-tuning과정에서도 마찬가지로 scaling을 거친다. 이후, SOTA anomaly detection method인 SR(Spectral Redisual)을 활용해 라벨을 생성한다. 라벨은 0, 1 값을 가진다. 알고리즘 형태는 다음과 같다.
<img src="https://velog.velcdn.com/images/_cykim_/post/345b5a32-bb11-4b30-b138-252328807ffe/image.png" alt="">
이 때, 이상치 지점을 slicing window의 평균지점으로 환산하여 정상 시계열로 변환해주는 작업을 거쳐서 학습한다. 이를 통해 보다 Robust하게 이상치를 탐지할 수 있게 된다. 모델이 이상치에 대해 오버피팅하는 것을 방지하고, 모델이 시계열 데이터의 일반적인 패턴을 학습하는 데 집중할 수 있도록 하는 것이다. 이렇게 하면 모델은 이상치가 아닌 다른 정상적인 데이터 포인트들로부터 패턴을 학습하고, fine-tuning된 모델은 실제 이상치를 더 잘 감지할 수 있게 된다.</p>
<h2 id="evaluation">Evaluation</h2>
<p>KPI dataset과 Yahoo dataset으로 실험을 진행했다. 
<img src="https://velog.velcdn.com/images/_cykim_/post/87ed7ea8-6f83-4a6b-8238-927196a3bb7b/image.png" alt="">
<img src="https://velog.velcdn.com/images/_cykim_/post/3b442869-1c8f-4036-8abd-89e72d879111/image.png" alt=""></p>
<p>Metrics의 경우 일반적인 anomaly detection처럼 precision, recall, F1-score을 기준으로 한다. 단, 각각의 timestamp를 확인하는 것 보다는 그 연속성을 반영하여 Trun와 False를 구분한다.
<img src="https://velog.velcdn.com/images/_cykim_/post/da648603-f51f-40a4-87f4-49112a31dd81/image.png" alt=""></p>
<p>Hyperparameter setting은 아래와 같다.
<img src="https://velog.velcdn.com/images/_cykim_/post/c61a7114-6ee3-46e7-9c1e-facd4bcfc919/image.png" alt=""></p>
<p>결과는 다른 Benchmark Model에 비해 좋은 성능을 보였다. 특히 당시 SOTA였던 SR+CNN보다 높은 성능을 보였다는 부분에서 충분한 의미가 있다고 생각한다.
<img src="https://velog.velcdn.com/images/_cykim_/post/23bd3c85-962f-4ad8-a846-3eaf8c3596bb/image.png" alt="">
<img src="https://velog.velcdn.com/images/_cykim_/post/6cdc0c1e-800b-487c-9eda-339f669c6862/image.png" alt=""></p>
<h2 id="conclusion">Conclusion</h2>
<p>TS-Bert의 성능, unsupervised learning이 가능하다는 점을 강조하면서 결론을 짓는다.
하지만 해당 논문이 풀어야 하는 숙제는 상당하다는 생각이 든다. Time series anomaly의 저차원적인 data point를 Transformer의 Encoder를 거쳐 고차원 공간에 mapping시키는 행위 자체가 상당히 비효율적이라는 생각이 들 뿐더러, 학습 시간도 꽤나 오래 걸릴 것이다. 그리고 앞에서도 언급한 것 처럼 엄밀히 따진다면 unsupervised learning이라고 보기 힘들다.</p>
<p>그럼에도 불구하고 연구적인 측면, 성능을 올리기 위해 transformer을 사용해보는 시도를 한 측면에서 충분히 읽어볼 가치가 있다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Imbalanced Target with AutoEncoder]]></title>
            <link>https://velog.io/@_cykim_/Imbalanced-target</link>
            <guid>https://velog.io/@_cykim_/Imbalanced-target</guid>
            <pubDate>Wed, 21 Feb 2024 15:18:38 GMT</pubDate>
            <description><![CDATA[<p>신용카드 이상 거래 탐지와 같은 task는 데이터의 불균형 정도가 매우 심하다
(정상 거래 : 이상 거래의 비율이 9 : 1인 경우가 대다수다). </p>
<p>Classification task에서 label의 불균형이 존재할 경우 보통 Oversampling과 Undersampling기법을 사용한다. </p>
<ul>
<li>Oversampling : 낮은 비율 클래스의 데이터 수를 늘림으로써 데이터 불균형을 해소</li>
<li>Undersampling :  불균형한 데이터 셋에서 높은 비율을 차지하던 클래스의 데이터 수를 줄임으로써 데이터 불균형을 해소</li>
</ul>
<p>하지만 Oversampling은 소수 클래스의 데이터를 억지로 늘리기에 모델이 해당 클래스에 대해 과도하게 최적화될 수 있으며, 데이터의 품질 또한 보장할 수 없다.(물론 GAN을 활용하는 등 생성 데이터의 정교화를 보장하기 위한 연구가 진행되지만 근본적으로 인공 데이터로서의 문제는 불가피할 것이다) Undersampling 또한 정보가 과도하게 손실될 수 있다.</p>
<p>그래도 <strong>label의 불균형을 해결하는 방법이 이 두 가지 말고는 없으려나?</strong> 하는 생각에 kaggle을 둘러보았다.</p>
<h2 id="autoencoder">AutoEncoder</h2>
<p>첨에 본 2개의 kaggle notebook은 모두 SMOTE를 활용한 Oversampling이었다. 정작 이것말고는 없나 싶을 때 해당 notebook을 발견했다.</p>
<p><a href="https://www.kaggle.com/code/shivamb/semi-supervised-classification-using-autoencoders/notebook">https://www.kaggle.com/code/shivamb/semi-supervised-classification-using-autoencoders/notebook</a></p>
<h3 id="autoencoder의-정의">AutoEncoder의 정의</h3>
<p>AutoEncoder의 개념을 보다 정확히 살펴보자
<img src="https://velog.velcdn.com/images/_cykim_/post/7dadacd0-f334-4fc6-a83e-8a42dc2d2289/image.png" alt=""></p>
<ul>
<li>AutoEncoder : 입력이 들어왔을 때 해당 입력 데이터를 압축시킨 다음, 데이터의 특징을 추출하여 다시 본래의 입력 형태로 복원시키는 신경망</li>
</ul>
<p>정의부터 얘기하면 Representation learning 작업에 신경망을 활용하도록 하는 Unsupervised learning이라고 한다. 때문에 Unlabeled data에 효과적이다. Unlabeled data를 의미있게 압축시켜 Representation으로 Encoding하는 Encoder와, 이를 다시 입력 데이터로 복원하는 Decoder로 이루어져 있다.</p>
<h3 id="autoencoder의-목적">AutoEncoder의 목적</h3>
<p>조금 더 자세히 들어가보자.</p>
<p>AutoEncoder를 사용하는 데는 두 가지 목적이 있을 수 있다.
    1) Manifold Learning
이는 Encoder를 학습시키는 것에 초점을 둔 것이다. Manifold Learning이라는 것은 결국 고차원 공간의 데이터를 저차원 Manifold space로 mapping시키는 함수를 찾는다는 것인데, 이는 입력 데이터만의 특성을 활용하여 어떠한 task를 수행한다는 느낌으로 받아들일 수 있다.
(Encoder 기반인 BERT는 분류와 같이 전체 문장의 이해를 요구하는 task에 적합한 것과 같은 맥락)</p>
<p>2) Generative model
윗 내용과 달리 Decoder를 학습시키는 것에 초점을 둔다. 실제 데이터의 분포를 학습하여 데이터를 생성한다. Latent variable을 실제 데이터의 분포로 mapping시키는 함수를 찾는다고 생각하면 된다.
(Decoder 기반인 GPT는 생성 요약과 같이 새로운 문장을 생상하는 task에 적합한 것과 같은 맥락)</p>
<p>종류로는 Sparse AutoEncoder, Denoising AutoEncoder, Contractive AutoEncoder 등이 있다고 하는데,, 이름만 난잡하지 결국 Regularization term을 부여하거나 Noise, Dropout을 추가하는 등 과적합을 방지하는 내용이다.</p>
<h2 id="autoencoder를-활용한-불균형-데이터-학습">AutoEncoder를 활용한 불균형 데이터 학습</h2>
<p>그럼 AutoEncoder로 불균형 데이터를 어떻게 다룬다는 걸까?
아이디어가 되게 재밌다. 
<img src="https://velog.velcdn.com/images/_cykim_/post/7b2d053f-2aa2-40c7-8d9a-f76f620d3c55/image.png" alt="">
해당 내용을 다룬 Kaggle Notebook에서 분석가가 Non-Fraud를 일부만 선택해 AutoEncoder로 돌린 결과를 시각화한 그림이다. t-SNE으로 차원축소한 데이터이고 초록색이 Non-Fraud, 빨간색이 Fraud이다. AutoEncoder를 돌리기 전은 Non-Fraud와 Fraud가 공간상에 겹쳐지는 부분이 있어 예측에 어려움이 있다. 하지만 Non-Fraud만 AutoEncoder를 통해 Representation으로 인코딩함으로써 저차원 Manifold Space로 압축시켰더니 오른쪽과 같은 결과가 나온다. 즉, Non-Fraud에 맞춤형으로 Latent code가 구성된 것이다. </p>
<p>그럼 추론할 때 Fraud에 해당되는 데이터를 입력한다고 하자. 모델은 Encoder를 사용해 Non-Fraud에 맞춰진 Latent code로 해당 데이터를 인코딩할 것이다. 이제 Decoder에서 Latent code를 다시 복원한다면 결과값은 입력값과 동일하지 않게 된다. 이러한 원리로 Fraud를 탐지할 수 있는 것이다.</p>
<h3 id="결론">결론</h3>
<p>한 가지 의문은,,, 참고한 Kaggle Notebook은 AutoEncoder을 통해 추출한 Non-Fraud Representation과 Fraud를 함께 분류기에 학습시킨다. Non-Fraud와 Fraud를 확실히 공간상에서 구분한 뒤 분류기로 학습한다는 방향인 것 같다. 결과가 잘 나오는 것 보니 이 또한 효과가 있어 보인다.</p>
<p>결론은 AutoEncoder 자체적으로 분류기로서 활용을 할 수도 있고, AutoEncoder로 Non-Fraud를 Representation한 값으로 분류기를 돌릴 수도 있는 것이다.
(과적합의 여지가 있어 보이지만 마땅한 근거가 떠오르지는 않는다. 나중에 생각나면 다시 와야지)</p>
<h2 id="reperence">Reperence</h2>
<p>AutoEncoder 설명 
<a href="https://www.youtube.com/watch?v=X8SBsVqmVdY">https://www.youtube.com/watch?v=X8SBsVqmVdY</a>
<a href="https://velog.io/@jochedda/%EB%94%A5%EB%9F%AC%EB%8B%9D-Autoencoder-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%A2%85%EB%A5%98">https://velog.io/@jochedda/%EB%94%A5%EB%9F%AC%EB%8B%9D-Autoencoder-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%A2%85%EB%A5%98</a>
AutoEncoder를 활용한 신용카드 이상거래 탐지
<a href="https://github.com/KerasKorea/KEKOxTutorial/blob/master/20_Keras%EC%9D%98%20Autoencoder%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%B4%20%EC%8B%A0%EC%9A%A9%EC%B9%B4%EB%93%9C%20%EC%9D%B4%EC%83%81%20%EA%B1%B0%EB%9E%98%20%ED%83%90%EC%A7%80%ED%95%98%EA%B8%B0.md">https://github.com/KerasKorea/KEKOxTutorial/blob/master/20_Keras%EC%9D%98%20Autoencoder%EB%A5%BC%20%ED%99%9C%EC%9A%A9%ED%95%B4%20%EC%8B%A0%EC%9A%A9%EC%B9%B4%EB%93%9C%20%EC%9D%B4%EC%83%81%20%EA%B1%B0%EB%9E%98%20%ED%83%90%EC%A7%80%ED%95%98%EA%B8%B0.md</a></p>
]]></description>
        </item>
    </channel>
</rss>