<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>o_o_gie.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 14 Mar 2023 12:29:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>o_o_gie.log</title>
            <url>https://velog.velcdn.com/images/o_o_gie/profile/c2b44768-e7b9-4b20-984b-58cefba3b09f/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. o_o_gie.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/o_o_gie" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[시스템 그게 돈이 됩니까?]]></title>
            <link>https://velog.io/@o_o_gie/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B7%B8%EA%B2%8C-%EB%8F%88%EC%9D%B4-%EB%90%A9%EB%8B%88%EA%B9%8C</link>
            <guid>https://velog.io/@o_o_gie/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B7%B8%EA%B2%8C-%EB%8F%88%EC%9D%B4-%EB%90%A9%EB%8B%88%EA%B9%8C</guid>
            <pubDate>Tue, 14 Mar 2023 12:29:42 GMT</pubDate>
            <description><![CDATA[<p>이번학기 시간표다. 
특징은 시스템 관련 수업을 3개 듣는다.</p>
<img src = https://velog.velcdn.com/images/o_o_gie/post/d944e854-d892-457b-be95-35d531095b23/image.jpg width = "40%" height = "30%">  




<p><strong>컴퓨터 시스템 개론</strong>
이름 그대로 시스템에 대한 개론과 어셈블리어 기초를 배운다.</p>
<p><strong>컴퓨터 아키텍쳐과목</strong>
이름 그대로 컴퓨터의 아키텍처(cpu, 메모리,gpu 등)를 배운다.</p>
<p><strong>멀티코어프로그래밍</strong>
시스템프로그래밍이라는 이름을 가지고 있던 과목으로 컴퓨터 시스템 개론에서 기초를 배웠다면 이 과목에서는 그것보다 어렵고 운영체제보다는 쉬운 내용을 배운다.</p>
<p>이번학기 기초머신러닝, 딥러닝 기초 수업 뿐 아니라 자연어 처리 교양 과목 등 인공지능 수업이 많이 개설되었다.</p>
<p>그런데 나는 <strong>시스템 쪽 수업</strong>을 선택했다. 전공학점을 채우는게 문제였으면 머신러닝/딥러닝 수업으로 전공학점을 채워도 됬지만 단순히 시스템을 듣고 싶어서가 이유다. </p>
<h2 id="인공지능을-버린건가-시스템-개발자가-목표가-된건가">인공지능을 버린건가? 시스템 개발자가 목표가 된건가?</h2>
<p>위 질문에 대한 대답은 “아니요”다. 오히려 반대다. 인공지능을 더 잘하고싶어서 시스템을 공부해야겠다 생각했다.</p>
<p>이 생각에 대해서 엄청난 확신이 있는 것은 아니지만 인공지능을 잘 하고 싶으면 시스템을 알아야겠다고 느꼈다.</p>
<p>나는 <strong>추상화와 Ops관점에서</strong> 시스템이란 무엇이고 왜 필요한지를  강렬하게 느꼈다. </p>
<h3 id="추상화란-무엇인가abstraction">추상화란 무엇인가(Abstraction)</h3>
<p>추상화에 대해서 처음 생각한 순간은 수업이 아닌 인공지능 오픈톡방에서 질문에 대한 대답을 고민하면서였다.</p>
<p>pytorch/tf의 softmax 함수에서 분류를 1로 잡으면 sigmoid함수와 같냐는 질문이었다. 순간 고민했다.</p>
<p>softmax와 sigmoid는 다중분류나 이진분류냐의 차이로 분류 대상이 다중이됬을 때 sigmoid수식을 일반화하면 softmax함수와 같다는 이론은 직접 수식으로도 증명해봤다. 그래서 말이되는 것 같다고 생각을 하고 있었다.</p>
<p>그때 현직 ML 엔지니어 분이 직접 실제로 그렇지 않다고 괜히 softmax와 sigmoid를 따로 둔 것이 아니며 내부 구현 코드를 보면 다르다고 하셨다. </p>
<p>이 경험에서  엔지니어링 측면을 외면하고 추상화된 개념에 매몰되어 있다고 생각했다. 결국 나는 logical 레벨이 아닌 physical 레벨의 AI를 구현하는 엔지니어를 희망하고 있는데 엔지니어링에 더 집중할 필요성을 느꼈다. 
이후 이런 함수부터 CNN의 커널 구현, lstm의 weight 구현 등 엔지니어링 측면에서 AI를 바라보고 AI에 바틀넥 또는 비효율성을 야기하는 요인에 대해서 고민하기 시작했다.</p>
<p>최근에는 AI 관점 뿐 아니라 더 넓고 기초적인 추상화들에 대해서도 고민했다. </p>
<p>python의 int는 왜 C언어에서 쓰는 int보다 범위가 넓을까?</p>
<p>python의 list,dictionary,heapq등의 자료형이 있다고 치면 왜 dictionary는 빠르고 좋은가 왜 heapq가 list sorting보다 빠르고 효율적인가? </p>
<p>자료구조의 관점 그리고 더 나아가 로우레벨(가령 어셈블리 레벨)에서 구현을 생각하기 시작했다.  </p>
<p>시스템 레벨에 대한 궁금증을 더 커지게 한 이유는 사실 다음 내용이 더 결정적이다.</p>
<h3 id="ops-관점에서-바라보기">Ops 관점에서 바라보기</h3>
<p>Ops 관점이라 썼는데, 사실 이 관점의 시작은 하드웨어부터 시작이었다</p>
<blockquote>
<p><strong>하드웨어? 갑자기 왠 하드웨어?</strong></p>
</blockquote>
<p>인공지능의 발전에 기여한 요인 2가지는 대부분 알듯 <strong>빅데이터와 하드웨어의 성장</strong>이다.</p>
<p>하드웨어 얘기를 해보자 gpu라는 병렬 처리에 미친듯이 빠른 하드웨어 덕분에 행렬곱의 가속화가 가능해졌다.</p>
<p>대부분의 딥러닝 계산이 거대한 행렬곱 연산으로 이루어지기에 학습 속도를 가속화했고 지금의 AI가 태어났다.</p>
<p>그렇게 nvdia gpu가 4천번대까지 발전했다. 그런데 이 변화는 일정한 방향의로의 발전이 아니다.</p>
<p>무슨 말이냐면 단순히 고정된 형태에서 연산을 빨리하는 하드웨어가 발전한 말이 아니라는 것이다.</p>
<p>TPU, NPU등이 새롭게 개발되고 있고 nvidia에서는 인공지능 전용 하드웨어 개발에 나섰다.</p>
<p>위 발전이 이루어진 이유를 잠깐 들춰보면, 구글의 TPU는 자신의 프레임워크 TF,jax에 최적화된 하드웨어 개발을 위해 만들어졌다. </p>
<p>인공지능 전용 gpu가 발전된 이유는 일반 게임용 gpu와 다른 성능 및 작동을 하는 gpu가 효율적이기 때문이다.</p>
<p>우리가 쉽게 생각하는 더하기, 곱하기 라는 연산도 하드웨어 수준에서 다르게 만들 수 있다. </p>
<p>그리고  그 하드웨어에 맞춰서 최적화해 연산을 최적화할 수 있다는 말이다. </p>
<p>괜히 직접 처음 데스크탑을 맞춰 프레임워크와 쿠다를 깔아 환경설정을 하는 부분이 큰 스트레스를 주는게 아니다.</p>
<p>딥러닝 성능 및 개발에 있어 이렇게나 큰 축을 담당하는 하드웨어의 지식없이 딥러닝 개발을 하겠다는 것이 나에게는 너무나 큰 맹점으로 느껴졌다.</p>
<blockquote>
<p><strong>그래서 하드웨어랑 Ops랑 무슨 상관인데?</strong></p>
</blockquote>
<p>MLOps, 이름부터 일단 굉장히 fancy한 이 기술은 뭘까?</p>
<p>MLOps의 이름에 홀렸는데 아는건 없어서 뭐하는 곳인지 궁금해서 MLOps오픈톡방에 들어간 나는 그냥 귓동냥이나 하는데, 이들의 주제는 모오옵시 다양하다.  </p>
<p>kuberflow(Ops의 핵심),fastapi,메모리 누수, 클라우드, 네트워크,데이터 베이스, 쿠다, gpu 병목 등등등 무수히 많은 주제였다. 나 혼자 자료도 찾아보고 고민을 하고 내린 결론은 MLOps는 그냥 ML 서비스를 서빙하는데 필요한 기술의 집합이었다. </p>
<p>실제로 한번은 MLOps 뭐냐에 대해 개발자들이 토론하는걸 잠깐 들었는데, 누군가가 </p>
<p><strong><em>“MLOps는 해당 기업, 부서, 또는 팀의 개발 문화 그 자체다”</em></strong></p>
<p>라고 말했고 많은 개발자 분들이 동의했다.</p>
<p>이런 생각들이 모여 내가 생각한 MLOps는 AI기술을 논문에서 끄집어 내 현실속 서비스로 만들어내는 기술이다.</p>
<p>인공지능 분야별로 조금씩 다르겠지만 일부 분야는 곧 AI 고유 분야보다 일단 벡엔드로 분류될 것이라고 생각하는 나이기에 더더욱 Ops관점에서 AI를 바라보았다. 다시 말하면 실제 데이터, 실제 환경, 실제 사용자를 대상으로 
AI가 어떻게 잘 작동할지를 고민하는 관점에서 바라보았다.  그리고 다음과 같은 생각들이 떠올랐다.</p>
<blockquote>
<p><strong>AI 서비스를 현실에 구현하기 위해서는 하드웨어를 알아야해.
AI 서비스를 현실에 구현하기 위해서는 하드웨어를 알아야해.
AI 서비스를 배포하기 위해 필요한 도커와 kebernetes 기술을 알아야해. 
이들을 알기 위해서는 클라우드를 이해해야해. 
클라우드를 이해하기 위해서는 기본적인 데이터 베이스와 네트워크를 알아야해.
도커 및 쿠버네티스 뿐 아니라 API기술을 활용해 실제 서비스를 배포하려면 메모리 개념을 알아야해.
메모리 개념을 알려면 하드웨어를 알아야해.</strong></p>
</blockquote>
<p>이런 생각에서 나의 시간표가 완성되었다.</p>
<p>나의 노션 소개에 이런 말을 써뒀다.</p>
<p><strong><em>이론상의, 현실적이지 않은, 실용적이지 않은 AI가 아닌</em></strong> </p>
<p><strong><em>실용적인 AI 서비스를 개발을 꿈꿉니다.</em></strong></p>
<p><strong>내가 시스템을 공부하는 이유는</strong>
<strong>AI가 논문 속에만 존재하지 않고 빅테크 기업의 무수한 자본속에서만 존재하는 기술이 아니라 믿기 때문이고.
그것을 실현하는 개발자가 되기 위해서이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[밑시딥3_자연스러운_코드로]]></title>
            <link>https://velog.io/@o_o_gie/%EB%B0%91%EC%8B%9C%EB%94%A53%EC%9E%90%EC%97%B0%EC%8A%A4%EB%9F%AC%EC%9A%B4%EC%BD%94%EB%93%9C%EB%A1%9C</link>
            <guid>https://velog.io/@o_o_gie/%EB%B0%91%EC%8B%9C%EB%94%A53%EC%9E%90%EC%97%B0%EC%8A%A4%EB%9F%AC%EC%9A%B4%EC%BD%94%EB%93%9C%EB%A1%9C</guid>
            <pubDate>Thu, 09 Feb 2023 11:45:52 GMT</pubDate>
            <description><![CDATA[<h3 id="이번에-다룰-내용은-제-2고지의-일부분이다">이번에 다룰 내용은 제 2고지의 일부분이다.</h3>
<p>원래 제 2고지를 한번에 리뷰하려고 했는데 그러자니 내용이 너무 많아서 한번 끊고 코드를 리뷰하고 나머지를 리뷰하려고 한다.</p>
<p>제 2고지는 자연스러운 코드로라는 제목을 가지고 있는데 이는 크게 <strong>가변 길이 인수를 처리하는 법,복잡한 계산그래프를 역전파하는 법 메모리 개선 및 기타 사용성 개선</strong>이다.</p>
<p>이번에는 그 중 가변 길이 인수 처리, 복잡한 계산 그래프 처리에 대해서 다룬다.</p>
<p>이전 글과 마찬가지로 <u>Variabl클래스</u>와 <u>Function클래스</u> 두개가 있으며 각각에 대해서 리뷰하겠다.</p>
<p>다만 순서를 전과 다르게 <strong>Function 부터 리뷰</strong>한다. 
그 이유는 이번 파트의 내용인 가변 길이 인수 처리와 복잡한 함수 미분은 모두 함수부분에서 내용을 바꾸고 그에 맞춰 Variable의 구현을 수정하는 식이기 때문이다. </p>
<p>그에 앞서 사용한 모듈에 대해서 잠깐 설명하자면 <strong>heapq와 weakref</strong>라는 python 표준 라이브러리가 사용되었는데 이는 사용된 파트에서 사용 목적과 함께 설명하겠다.</p>
<pre><code class="language-python">import numpy as np
import heapq
import weakref</code></pre>
<h3 id="function-class">Function Class</h3>
<pre><code class="language-python">def as_array(x): 
    if np.isscalar(x):
        return np.array(x)
    return x

class Function: 
    def __call__(self,*inputs): #가변 인자로 처리한다. *을 붙이면 가변 인자들이 tuple형태로 저장된다. 
        xs = [x.data for x in inputs]
        ys = self.forward(*xs) #forward 부분에서 unpack : 입력이 [x0,x1] -&gt; x0,x1으로 각각 입력하도록 만든다
        if not isinstance(ys,tuple): #tuple이 아닌 경우 tuple로 만들어준다.
            ys = (ys,)
        outputs = [Variable(as_array(y)) for y in ys]
        self.generation = max([x.generation for x in inputs])# inputs generation들 중 최대 값을 generation으로 지정
        for output in outputs:
            output.set_creator(self)
        self.inputs = inputs 
        #self.outputs = outputs
        self.outputs = [weakref.ref(output) for output in outputs]
        #약한 참조를 이용하여 순환 참조를 막아준다
        #output과 creator간의 이중 참조구조를 막아주는 것
        return outputs if len(outputs) &gt; 1 else outputs[0] #원소가 하나라면 굳이 리스트 형태가 아닌 한개의 원소로 반환

    def __lt__(self,other):
        if self.generation &gt;= other.generation:   #오름차순
            return True
        else:
            return False

    def forward(self,xs):
        raise NotImplementedError()

    def backward(self,gys):
        raise NotImplementedError()

class Add(Function):
    def forward(self,x0,x1):
        y= x0 + x1
        return y
    def backward(self,gy):
        return gy,gy #더하기 연산상 global grad 그대로 전달

def add(x0,x1): #편하게 함수화
    return Add()(x0,x1)

class Square(Function):
    def forward(self,x):
        y = x**2
        return y
    def backward(self,gy):
        x = self.inputs[0].data #일변수 함수이므로 inputs의 첫번째 원소로 x를 지정
        gx = 2*x*gy
        return gx

def square(x):
    return Square()(x)
</code></pre>
<h3 id="1def-callself_inputs-_">1.def <strong>call</strong>(self,_*inputs) _:</h3>
<p>파이썬을 조금 관심있게 사용해본 사람이라면 사실 함수의 가변 길이 인자를 활용하는 법은 알 것이다. </p>
<p><strong>인자에 *를 붙이면 가변 길이의 인자를 tuple 형태</strong>로 묶어서 함수 내에서 사용할 수 있다. 
위 경우 inputs가 함수 내에서 가변 길이 인자를 원소로 가지는 tuple이 된다. </p>
<p>따라서 필자는 이 tuple을 사용해 가변 길이 인자를 리스트로 만들어 사용한다.
xs = [x.data for x in inputs] 와 같이 <strong>list comprehension</strong>으로 input들을 리스트 xs에 저장한다.</p>
<p>그리고 이 <strong>리스트를 forward 함수에 흘려보내서 ys</strong>라는 결과를 얻는다.
이때 인자를 unpack해서 넣고 나온 ys라는 결과를 Variable 객체 리스트로 만들어서 outputs로 지정한다.</p>
<p>그리고 generation 변수가 추가되는데 이 변수는 함수의 위계를 나타내기 위한 변수이다. 이것이 필요한 이유를 아래 계산 그래프를 통해 보자.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/1324293c-dc62-456b-8cac-6a147c7e9106/image.jpeg" alt=""></p>
<p>위 그래프를 역전파할 때 dA를 구하기 위해서는 dB,dC의 grad를 모두 구하고 더한 값을 흘려보내야한다. </p>
<p>따라서 역전파 연산 순서가 <strong>D-&gt;B,C-&gt;A</strong>순이어야 한다.
그런데 기존에 구현한 코드는 상위 함수를 리스트에 우선 순위 없이 더하고 마지막 원소부터 pop하므로(일종의 stack구조)연산 순서가 
<strong>D-&gt;B-&gt;A-&gt;C-&gt;A 또는 D-&gt;C-&gt;A-&gt;B-&gt;A</strong>가 된다. 
이렇게 복잡한 연산을 위해서는 앞으로 함수별 계산의 위계가 필요하다는 것이다. 이를 위해 함수와 변수에 generation 변수를 부여한다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/cbf4eac8-9578-4b2c-bcec-0f15ecf499ea/image.jpeg" alt=""></p>
<p>위처럼 <strong>generation은 0부터 시작해서 단계별로 높여</strong>가는 구조이다.
0부터 시작하고 변수에 대한 함수에 같은 generation을 부여한다
가령 a=A(x) 라면 A는 x의 generation을 따라가고 a는 A+1이다.
만약 다변수 함수라면, 변수 중 가장 높은 세대를 부여한다.</p>
<p>그 이후로는 inputs와 outputs를 지정하고 inputs에게 각각 creator를 설정한다. 그리고 outputs 원소가 하나라면 리스트로 반환할 필요가 없으므로 [0]으로 반환한다.</p>
<h3 id="2addsquare-">2.Add&amp;Square :</h3>
<p>이전과 같이 더하기, 제곱 연산을 수행하는 내용을 forward에 구현하고 이에 대한 미분을 backward에 구현한다. 
x0+x1 연산은 각 변수에 대한 편미분값이 1이므로 gy를 2개(x0,x1각각) 반환하고 square은 를 반환한다</p>
<h3 id="3lt">3.<strong><strong>lt</strong></strong>:</h3>
<p>이 부분은 Function Class의 연산자를 오버로딩하는 것인데 왜 이부분이 필요한지는 Variable Class에서 설명한다. </p>
<h3 id="variable-class">Variable Class</h3>
<p>여기서부터 앞서 구현한 Function Class를 이용하여 역전파를 구현하기 위해 Variable Class를 어떻게 바꿨는지 알아보자</p>
<pre><code class="language-python">class Variable():
    def __init__(self,data):
        self.data = data
        if data is not None: 
            if not isinstance(data,np.ndarray): 
                raise TypeError(f&quot;{type(data)}는 지원하지 않습니다.&quot;)
        self.grad = None
        self.creator = None 
        self.generation = 0 # 세대를 기록하는 변수
    def set_creator(self,func):
        self.creator = func #부모를 설정할 때 세대가 하나 증가하는 것이므로 1을 더한다
        self.generation = func.generation + 1 
    def backward(self):
        if self.grad is None: 
            self.grad = np.ones_like(self.data)
        funcs = [] #함수 리스트들
        seen_set = set() #이미 본 함수인지를 확인하는 집합
        def add_func(f): #중첩 함수로 사용해서 함수를 더한다
            if f not in seen_set: #집합에 없다면 
                heapq.heappush(funcs,f)
                seen_set.add(f)
        add_func(self.creator)

        while funcs: 
            f = heapq.heappop(funcs)
            gys = [output().grad for output in f.outputs] #output grads를 list로 저장
            #weakref로 참조시 이렇게 ()선언해야지 원하는 값이 output이 나온다
            gxs = f.backward(*gys) #리스트를 unpack해서 집어넣음
            if not isinstance(gxs,tuple): # 
                gxs = (gxs,)
            for x,gx in zip(f.inputs,gxs): 
                if x.grad is None: #grad가 없으면 값을 입히고
                    x.grad = gx
                else: #grad가 있는 경우에는 거기에 값을 덮어 씌운다. 이렇게하면 하나의 변수에서 여러개의 grad 고려가능
                    x.grad =x.grad + gx
                    #x.grad +=gx #이렇게 연산하면 다르게 나온다 왜??
                if x.creator is not None: 
                    add_func(x.creator) #func.append에서 우선순위에 맞게 넣도록 add_func사용

    def cleargrad(self):
        self.grad = None
</code></pre>
<h3 id="1init">1.init:</h3>
<p>Function class에서 언급했듯 함수 뿐 아니라 변수도 generation이 필요하므로 클래스 변수에 추가한다.</p>
<h3 id="2set_creator">2.set_creator:</h3>
<p>set_creator의 역할은 해당 변수의 부모 함수를 저장하는 역할이다. generation을 구현하기 위해서 이 과정에서 변수의 generation을 <strong>부모함수의 generation + 1</strong>로 할당한다.</p>
<h3 id="3backward">3.backward:</h3>
<p>backward에서 구현해야하는 핵심적인 문제는 
<strong>첫 번째, generation 위계에 맞춰서 역전파가 이루어지도록 한다.</strong>
<strong>두 번째, 하나의 변수에게 오는 여러개의 global grad를 처리한다.</strong>
<img src="https://velog.velcdn.com/images/o_o_gie/post/6285fb08-6758-4b8d-b928-e5278483396c/image.jpeg" alt=""></p>
<p>두 번째는 위 처럼 x+x연산을 미분할 때, 1과 1이 흘러오는 global grad인데 이를 x에 더하는 식으로 구현해야 한다는 말이다.</p>
<p>첫 번째를 구현하기 위해서 <strong>seen_set 집합과 add_func(f)함수</strong>를 활용한다. add_func()함수는 이전에 단지 funcs리스트에 append를 하는 것이 아닌 함수를 추가하면서 generation을 기준으로 정렬된 funcs를 만드는 역할을 한다. </p>
<p>seen_set은이미 연산된 함수는 funcs리스트에 추가하지 않도록 하는 역할을 한다. 이게 없다면 아래 그림에서 B,C의 creator인 A의 역전파가 두 번 발생한다. 
<img src="https://velog.velcdn.com/images/o_o_gie/post/3bce460b-06fd-44fe-8ac2-cbe924d90457/image.jpeg" alt=""></p>
<p>교재에서는 add_func함수를 아래와 같이 구현한다.</p>
<pre><code class="language-python">def add_func(f): #중첩 함수로 사용해서 함수를 더한다
            if f not in seen_set: #집합에 없다면 
                funcs.append(f) #함수를 추가하고
                seen_set.add(f) #집합에도 추가한다 -&gt; 중첩 미분을 막음
                funcs.sort(key = lambda x:x.generation) #generation에 따라 정렬
        add_func(self.creator)</code></pre>
<p>직관적으로 리스트에 함수를 append하고 리스트 내 함수들의 generation을 기준으로 정렬한다. 하지만 generation이 큰 함수를 리스트에서 꺼내기 위해 수많은 함수들에 대해서 하나하나 추가하고 정렬하는 과정은 <u>비효율적이고 시간이 오래 걸린다</u>.
이때 <strong>우선 순위 큐를 활용하면 보다 효율적이고 빠르므로 나는 <u>python의 heapq</strong></u>를 사용했다.</p>
<p>다만 heapq는 sort 메소드처럼 key를 지원하지 않는다. 즉 비교연산을 통해서 우선순위를 계산하는데 리스트에 넣을 Function 객체는 객체이므로 비교가 안된다. </p>
<p>이를 위해서 앞서 언급한 <strong>Function Class의 비교 연산자(__</strong>lt____)를 오버로딩__한다.</p>
<pre><code class="language-python"> def __lt__(self,other):
        if self.generation &gt;= other.generation:
            return True
        else:
            return False</code></pre>
<p>위의 코드를 통해 우리는 Function 객체를 직접 대소비교할 수 있으며 이것의 기준은 객체의 generation이 된다. 추가로 generation이 같은 경우는 연산 순서와 상관 없으므로 신경쓰지 않는다.</p>
<p>이로써 우리는 함수 리스트를 generation을 기준으로 넣고 뽑을 수 있다.</p>
<p>이제 <strong>while funcs 내부</strong>를 보자. heapq를 사용하고 있으므로 heapq.pop()메소드로 함수를 가져오며 이 함수의 output에 대한 grad를 리스트 gys로 저장한다. 여기서 앞선 언급한 backward에서 구현해야하는 두 번째 요소를 구현한다. 여러 방향에서 오는 global grad를 리스트로 저장한다. 그리고 함수의 backward에 통과시켜서 grad를 gxs로 저장한다. 이 gxs는 f.inputs리스트의 변수 객체와 상응한다. </p>
<p>가령 <strong>y=f(a,b)</strong>함수가 있다면 f의 inputs는 (a,b)이며 gxs는 (ga,gb)로 각각 변수와 그에 대한 grad로 쌍을 이룬다.
이를 이용해 f.inputs,gx를 zip으로 묶어 반복문을 돌리고 결과적으로 함수의 input에게 그에 대한 gx를 grad를 전달한다.
이때 <strong>x.grad가 없으면 당연히 gx를 할당</strong>하고 <strong>이미 x.grad가 있다면 할당하는게 아닌 거기에 값을 더하도록 구현</strong>해서 두 번째 문제를 해결한다. 그리고 creator가 있다면 add_func으로 추가한다.</p>
<p>이때 재밌는 점을 grad를 더할 때 아래 두 코드의 결과가 다르다는 것이다. </p>
<pre><code class="language-python">x.grad =x.grad + gx
x.grad +=gx </code></pre>
<p>파이썬은 두 번째 줄과 같이 할당연산자를 지원하며 기본 연산 후 할당의 축약형이라고 알려져도 있는데 <strong>내부 구현이 달라서 결과적으로 값이 달라진다.</strong> 
이는 다음 글에서 다룰 메모리 효율화와 연결지점이 있으므로 다음글에서 다뤄보겠다.</p>
<h3 id="4cleargrad">4.cleargrad:</h3>
<p>마지막 cleargrad는 같은 변수를 이용해서 연산을 다시 하는 경우에 <strong>grad값을 다시 None</strong>으로 만들어준다.</p>
<h2 id="마무리">마무리</h2>
<p>지금까지 2장의 절반이 조금 넘는 내용을 리뷰했다. 역시 책을 따라서 하나하나 구현하는게 전반적인 이해에 도움이 되는 것 같다.
다음 글에서는 메모리 효율화를 위해 weak_ref를 이용하는 방법과 할당연산자가 왜 다른결과를 내는지에 대해서 다뤄보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[밑시딥3_역전파_구현_솔루션]]></title>
            <link>https://velog.io/@o_o_gie/%EB%B0%91%EC%8B%9C%EB%94%A53%EC%97%AD%EC%A0%84%ED%8C%8C%EA%B5%AC%ED%98%84%EC%86%94%EB%A3%A8%EC%85%98</link>
            <guid>https://velog.io/@o_o_gie/%EB%B0%91%EC%8B%9C%EB%94%A53%EC%97%AD%EC%A0%84%ED%8C%8C%EA%B5%AC%ED%98%84%EC%86%94%EB%A3%A8%EC%85%98</guid>
            <pubDate>Tue, 31 Jan 2023 06:58:21 GMT</pubDate>
            <description><![CDATA[<h3 id="밑바닥부터-시작하는-딥러닝3는-현대-딥러닝-프레임워크와-유사한-dezero-프레임워크를-파이썬으로-직접-구현하는-책이다"><strong>밑바닥부터 시작하는 딥러닝3는 현대 딥러닝 프레임워크와 유사한 DeZero 프레임워크를 파이썬으로 직접 구현하는 책이다.</strong></h3>
<h3 id="책에서-단계별로-필요한-기능을-구현하는데-아래-코드는-10단계의-마지막-코드로-실제-공부를-하시는-분들은-1단계부터-직접-고민하고-짜보는걸-추천한다">책에서 단계별로 필요한 기능을 구현하는데, 아래 코드는 10단계의 마지막 코드로 실제 공부를 하시는 분들은 1단계부터 직접 고민하고 짜보는걸 추천한다.</h3>
<h4 id="첫-번째-파트는-variable-클래스를-만드는-파트이다">첫 번째 파트는 Variable 클래스를 만드는 파트이다.</h4>
<h4 id="variable은-우리가-만드는-dezero-프레임워크의-데이터-타입이라고-볼-수-있다">Variable은 우리가 만드는 <strong>DeZero 프레임워크의 데이터 타입</strong>이라고 볼 수 있다.</h4>
<h4 id="upytorch의-tensoru-같은-역할이라고-볼-수-있다"><u>pytorch의 tensor</u> 같은 역할이라고 볼 수 있다.</h4>
<p>기본적으로 DeZero는 <strong>numpy ndarray</strong>로 연산을 수행한다. 
따라서 Variable 내에서 ndarray가 아닌 data type에 대해 <strong>예외처리</strong>한다.</p>
<ol>
<li>as_array()함수는 <strong><u>numpy scalar를 ndarray로 변환</u></strong>하는 함수인데, 
이는 <strong>numpy 연산의 결과로 scalar</strong>값이 나올 때 ndarray를 scalar로 변환하기 때문에 필요하다.
e.g.2(ndarray)**3 = 8(np.float64) -&gt; 8를 ndarray로 변환해야함</li>
</ol>
<ol start="2">
<li><strong>self.data</strong> : 클래스의 ndarray로 저장하는 값에 해당하는 부분이다. 가령 input이 2라고 하면 이 input또한 Variable 객체인데 이 2라는 값이 self.data에 해당한다.
<strong>self.grad</strong> : <u>data의 loss에 대한 gradient</u>를 저장한다.
오차 역전파 과정 상 <strong><u>각 데이터에 대해서 gradient를 추적하고 저장하고 있어야함</u></strong>을 알 수 있는데 이를 클래스 변수로 저장한다. 
(<a href="https://velog.io/@o_o_gie/cs231n%EC%98%A4%EC%B0%A8%EC%97%AD%EC%A0%84%ED%8C%8C">오차역전파 정리글</a>)
<strong>self.creator</strong> : 해당 변수를 낳는 함수라는 의미에서 creator라고 지칭한다.
계산그래프로 봤을 때, 변수와 함수의 creator 관계는 다음과 같다. 
<strong>self.creator를 씀으로써 해당 변수를 낳은 이전 함수를 추적할 수 있다.</strong>
<strong>set_creator</strong>로 creator를 지정하는데 이때 인자 func은 이후 설명할 Function 클래스 객체이다. <img src="https://velog.velcdn.com/images/o_o_gie/post/520afebe-77fe-47d7-9f72-18a60fa6d5a0/image.jpeg" alt=""> </li>
<li><strong>backward()</strong> : 역전파를 자동화하는 메소드다. 기본적인 생각은 creator를 리스트로 집어넣어서 빈 리스트가 될 때까지(더 이상 이전의 연산이 없을 때까지) chain rule로 grad를 구하고 저장한다.
이를 구현하기 위해 우선 self.grad가 None일때는 1로 지정한다.
이는 <strong>첫 번째 gradient(dL/dL에 해당)를 자동으로 1로 설정</strong>하기 위함이다. ones_like를 사용하는 이유는 data와 같은 type,shape를 가져가기 위해서이다. 그리고 함수 리스트 funcs를 만드는데 첫 번째 원소는 self.creator로 지정한다. 그리고 funcs가 빈리스트가 아닌 동안 funcs.pop()으로 가장 최근의 Function 객체를 f로 지정한다. 
해당 객체의 <strong>input,output 변수를 x,y로 지정하고 y의 grad를 이용해 x의 grad를 구한다</strong>. (<u>이에 대한 구현은 Function 클래스 참조)</u>
그리고 creator가 존재한다면(이전 연산이 존재한다면) 그 연산을 funcs리스트에 추가함으로서 구현한다.</li>
</ol>
<pre><code class="language-python">import numpy as np


def as_array(x): #제곱등의 넘파이 연산시 ndarray에서 np float 64등으로 바뀌게 되는데 이를 방지하기 위해서 as_array선언
                 #Function 클래스의 Forward결과를 Variable 객체로 상속할때 사용한다. Forward에 사용되는 넘파이 연산이 스칼라값을 반환하기 때문
    if np.isscalar(x):
        return np.array(x)
    else: 
        return x

class Variable():
    def __init__(self,data):
        self.data = data
        if data is not None: 
            if not isinstance(data,np.ndarray): #ndarray타입만 지원하기 위한 예외처리 
                raise TypeError(f&quot;{type(data)}는 지원하지 않습니다.&quot;)
        self.grad = None
        self.creator = None #계산 그래프상 이전 함수를 follow up 하기 위한 클래스 변수
    def set_creator(self,func):
        self.creator = func #해당 변수를 만든 함수 객체를 지정해주는 부분
    def  backward(self):
        if self.grad is None: #dy/dy는 무조건 1인데 이를 연산시에 지정하고 싶지 않으므로 이렇게 지정해둔다
            self.grad = np.ones_like(self.data) #ones_like를 쓰는 이유는 데이터 타입까지 따라가기 위해서
        #위 코드의 구체적 설명 : 현재 코드는 y라는 아웃풋을 가지고 함수의 backward()를 이용해 x라는 인풋의 grad를 구하면서 역전파를 수행
        #근데 가장 먼저 알아야하는 grad인 dy/dy의 경우 사실 무조건 1이다. 이를 따로 지정하지 않으면 y라는 객체를 처음 생성했을 때 grad가 None이 되는데 (Variable의 init 참조)
        #이 값이 무조건 1이 되어야하므로 None인 경우에 grad를 1로 지정한다. 이 dy/dy에 해당하는 grad를 제외하고는 chain rule에 의해 None이 아닌 가지게 되므로 원하는 역할을 수행한다.

        funcs = [self.creator] #함수를 리스트로 저장해서 역전파 구현
        while funcs: #이전에 연산한 함수가 있느 동안 계속 실행
            f = funcs.pop() #존재하는 가장 최근의 연산을 pop해서 지정
            print(f)
            x = f.input #해당 함수로의 인풋값
            y = f.output #해당 함수에서의 결과값 지정
            x.grad = f.backward(y.grad) #Function 객체에서의 backward
            if x.creator is not None: #만약 이전 함수가 있다면 즉 추가로 역전파할 함수가 있다면
                funcs.append(x.creator) #해당 함수를 funcs리스트에 추가</code></pre>
<h3 id="다음은-function-클래스에-대한-설명이다">다음은 Function 클래스에 대한 설명이다.</h3>
<h4 id="function-클래스를-함수의-연산과-미분을-구현한다">Function 클래스를 함수의 연산과 미분을 구현한다.</h4>
<h4 id="이때-기본적인-큰-틀인-function이라는-함수-백본을-만들고-이를-상속받아서-구체적인-연산과-미분을-담는-식으로-구현한다">이때 기본적인 큰 틀인 Function이라는 함수 백본을 만들고, 이를 상속받아서 구체적인 연산과 미분을 담는 식으로 구현한다</h4>
<ol>
<li><strong>백본</strong> : 클래스를 불렀을 때 작동할 수 있도록 call 메소드를 사용하며 인자로 input을 받는데, 이는 Variable 객체이다. 
따라서 연산을 수행할 때는 input.data를 x에 할당하고 이를 forward에 통과시킨다.
이 forward가 상속받아서 구현하는 함수의 구체적 내용이다. 
통과시킨 결과를 <strong>Variable 객체로 만들고 이 객체의 creator는 self를 준다</strong>. 
output이라는 객체를 만드는데 현재 <u>Function 객체를 사용한 것이므로 self를 creator로</u> 지정한다.
또한 함수 상 <strong>input과 output도 클래스 변수로 저장</strong>해준다. 
이를 통해 Function 객체만을 가지고 chain rule을 적용하는 Variable 클래스의 backward 메소드를 구현할 수 있다.
<strong>forward,backward</strong> : 백본상에서는 forward와 backward에 대한 내용을 작성하지 않는다.</li>
<li><strong>Square,Exp</strong> : 이 두개의 클래스는 단순하게 제곱,exp를 수행하는 클래스인데 중요한 것은 이러한 <strong>연산을 forward에 구현하고 backward에서는 이에 대한 미분 계산을 구현</strong>한다. 
즉 forward에서 x^2를 구현했다면 backward에서는 이것의 미분인 2*x를 구현한다. 
backward를 수치 미분으로 구현하면 컴퓨터상 발생하는 수치적 오차가 발생하므로 직접 forward의 내용에 맞춰 구현한다. </li>
<li><strong>square(),exp()</strong> : Function 객체의 선언과 사용을 간단하게 하기 위해서 만든 함수이다.</li>
</ol>
<pre><code class="language-python">class Function: #순전파와 역전파를 구현하는 클래스
    def __call__(self,input): #Variable 객체를 인자로 받는다
        x = input.data
        y = self.forward(x) #forward의 결과를 y로 저장
        output = Variable(as_array(y)) #이를 ndarray로 바꾸고 Variable객체 output 생성
        output.set_creator(self) #이 생성된 output의 creator(함수)는 자기자신이므로 self를 지정
        self.input = input #input
        self.output = output #output을 클래스 변수로 지정해두는데 이는 역전파를 할때 용이하게 하기 위함
        return output

    def forward(self,x): #forward와 backward의 구체적 내용은 상속을 통해 지정한다
        raise NotImplementedError()

    def backward(self,gy):
        raise NotImplementedError()


#Function 클래스를 상속받는다. 구현하고 싶은 함수의 내용을 forward, 그 forward의 미분 공식을 backward에 구현한다.
class Square(Function): 
    def forward(self,x):
        return x**2
    def backward(self,gy):
        return gy*(self.input.data)*2


class Exp(Function):
    def forward(self,x):
        return np.exp(x)
    def backward(self,gy):
        return gy*np.exp(self.input.data)

def square(x): #함수로 간소화
    return Square()(x) #function 객체를 반환함은 여전하다

def exp(x):
    return Exp()(x)</code></pre>
<h4 id="여기까지가-1고지의-내용이다">여기까지가 1고지의 내용이다.</h4>
<h4 id="아직-0차원-ndarray계산이고-행렬곱등-딥러닝에서-필요한-연산들이-나오지-않았지만-오차역전파-자동화라는-중요한-과정을-구현했다">아직 0차원 ndarray계산이고, 행렬곱등 딥러닝에서 필요한 연산들이 나오지 않았지만 오차역전파 자동화라는 중요한 과정을 구현했다.</h4>
<h4 id="이후-내용에서-나머지-단계들을-어떻게-구현할지-궁금해진다">이후 내용에서 나머지 단계들을 어떻게 구현할지 궁금해진다.</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[cs231n_오차역전파]]></title>
            <link>https://velog.io/@o_o_gie/cs231n%EC%98%A4%EC%B0%A8%EC%97%AD%EC%A0%84%ED%8C%8C</link>
            <guid>https://velog.io/@o_o_gie/cs231n%EC%98%A4%EC%B0%A8%EC%97%AD%EC%A0%84%ED%8C%8C</guid>
            <pubDate>Tue, 31 Jan 2023 06:00:46 GMT</pubDate>
            <description><![CDATA[<p>이번에 리뷰할 것은 <strong>오차역 전파(BackPropagation)</strong>이다.
딥러닝에서 중요한 개념이며 모르면 딥러닝의 전반 과정을 이해할 수 없다고 생각한다. 
왜 중요한지를 알기 위해서 언제 쓰이는지를 알아야한다.</p>
<p>오차역전파는 학습 과정에서 사용한다. 딥러닝도 학습이라는 뜻인데,</p>
<p>뭘 학습하는가? 물론 모델마다 아주 약간은 다르겠지만 <u>가중치</u>를 학습하는 것이다.</p>
<p>모든 모델은 가중치를 학습한다. 가중치를 다시 한번 정리해보자면,</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/43ec1744-3afe-4d64-8404-e74ca6bb1a24/image.png" alt=""></p>
<p>위 처럼 하나의 이미지값이 고양이일 값, 강아지일 값, 배일 값 각각을 계산하기 위해서 주어지는 영향력이다.</p>
<p>정말 잘만들어진 가중치는 고양이 이미지에 대해서 고양이 점수를 압도적으로 높게 계산하며 나머지 점수는 낮게 부여할 것이다. 우리는 학습을 통해 이런 좋은 가중치를 구해내고 싶다.</p>
<p>그럼 어떻게 구해낼 것인가? 이게 관건인데, 우리는 loss 값(loss fuction 참조)이 낮은게 좋은 모델이라는 것을 안다. 그래서 loss fuction를 최소화하는 가중치를 점차 찾아나가면 된다.</p>
<p>무슨 말이냐, 우리는 이 복잡한 딥러닝을 아주 간단하게</p>
<h3 id="fw1x1w2x2--loss"><strong>f(w1,x1,w2,x2) = loss</strong></h3>
<p>로 표현해볼 수 있다. 이 모델은 가중치 2개 x input이 2개이다.</p>
<p>이때 dL(loss의 약자 이하 L로 씀 )/dw1을 구하면 된다. 
<u><strong>w1이 h만큼 변했을 때 loss가 얼마나 변하냐?</strong></u> 를 구하면 되는 것이다. </p>
<p>근데 우리가 쓸 모델들은 파라미터가 적게는 수십만 많게는 억단위인데</p>
<p>계산하기란 쉽지않다. 근데 이걸 해내게 하는것이 back propagation이다.</p>
<p>아래 그림을 보자</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/5cf905b7-5023-4234-a344-a163625c459f/image.png" alt=""></p>
<p>f = z(x+y) 라는 함수이다. 이때 x,y,z가 f값에 가지는 변화량을 궁금하다면 우리는 어떻게 해야할까?</p>
<p>우리가 쉽게 구할 수 있는건 dq/dx , dq/dy, df/dz, df/dq 이다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/9da8d128-2bbc-413e-a600-85e26617d33b/image.png" alt=""></p>
<p>그런데 소위 chain rule에 의해서</p>
<p>df/dx = df/dq * dq/dx가 되고 df/dx를 구할 수 있다.</p>
<p>위는 너무 간단한 식이라 이런게 왜 필요하나 싶을 텐데 아래의 그림을 보자.</p>
<p>아래는 2개의 가중치 2개의 input data, 하나의 bias를 가진 노드에 시그모이드 함수를 취하는 함수이다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/954d750d-c7cb-460c-8b68-92a899c203ba/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/4fe46db1-787f-459a-ba13-59ae870c399c/image.png" alt=""></p>
<p>이렇게 복잡할 때 우리는 chain rule을 활용하여야지만 w0,w1,w2의 gradient를 각각 구할 수 있다.</p>
<p>오차역 전파는 이름부터 그렇듯 가장 끝 노드에서 시작한다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/4d4ee052-84de-4ea0-bf70-bccfab851ac5/image.jpg" alt=""></p>
<p>마지막 두개의 노드를 각각 k와 f라고 이름지어보자. 위에 초록색으로 표시된 값은 각 노드를 지난 후에 값이다.</p>
<p>그리고 아래 붉은색이 각 k와 f의 L(우리는 결국 loss fuction에 대한 변화량을 구하는 것이다 그래서 L로 쓴다)에 대한 gradient값이다.</p>
<p>dL/df 은 마지막 노드이기에 무조건1이다. 또한 우리는 k의 식을 알기 때문에 df/dk를 구할 수 있다.</p>
<p>그리고 dL/dk를 구해야하는데</p>
<p>이때 chain rule에 의해 
<u><strong>dL/dk = df/dk(local gradient) * dL*df(global gradient)로 표현된다.</strong></u></p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/ee513665-095d-48c4-bffa-e33e7ec6ab8c/image.jpg" alt=""></p>
<p>이번엔 exp함수를 e라고 했을 때 앞서와 똑같이 dL/de을 구하는 과정이다 이런과정을 반복했을 때,</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/6c2ce2ea-1a5d-4564-b1d5-4ec8c3f0e617/image.jpg" alt=""></p>
<p>위 그림처럼 우리가 관심을 가지고 있던 가중치의 gradient값을 구할 수 있게된다.</p>
<p>여기까지가 딥러닝의 주된 계산 중 하나인 오차역전파(back propagation)이다.</p>
<p>수식이 많아서 그렇지 결국 정리를 해보자면, 우리는 좋은 가중치를 찾아서 손실함수를 최소화 하고싶다.</p>
<p>좋은 가중치를 찾는 과정이 곧 학습인데 학습의 지표가 될 것이 각 가중치의 손실함수에 대한 gradient이다.</p>
<p>이 gradient를 구하기 위해서 맨 끝의 노드에서부터 chaine rule을 사용해 천천히 계산해 오는 것이 오차 역전파다.</p>
<p>이 오차역전파를 실제 구현할 때 관건이 되는 부분은 
<u><strong>각 함수의 연산과 함수의 input값을 저장하고 있어야 한다</strong></u>는 것이다.
dL/dL = 1부터 시작해서 모든 함수와 변수에 대해서 chain rule을 적용하기 위해서는 모든 변수와 함수를 저장해야할 것이다</p>
<p>﻿</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NLP_word2vec(1)]]></title>
            <link>https://velog.io/@o_o_gie/NLPword2vec1</link>
            <guid>https://velog.io/@o_o_gie/NLPword2vec1</guid>
            <pubDate>Sun, 15 Jan 2023 06:08:18 GMT</pubDate>
            <description><![CDATA[<h4 id="자연어-처리에서-우선적으로-고민해야할-문제는-자연어를-어떻게-컴퓨터가-처리할-수-있도록-만드는가이다-쉽게-말해-자연어-자체를-컴퓨터가-이해할-수-없으니-인코딩해야하는데-어떤-방식을-취하냐이다-결국-이-인코딩을-word-to-vector라고-생각해볼-수-있다">자연어 처리에서 우선적으로 고민해야할 문제는 자연어를 어떻게 컴퓨터가 처리할 수 있도록 만드는가이다. 쉽게 말해 자연어 자체를 컴퓨터가 이해할 수 없으니 인코딩해야하는데 어떤 방식을 취하냐이다. 결국 이 인코딩을 word to vector라고 생각해볼 수 있다.</h4>
<h4 id="이-문제가-어려운-이유는-자연어-자체가-굉장히-복잡하고-이해하기-어려우며-맥락-의존적이기-때문이다-같은-말이라도-다양한-의미가-있고-같은-의미라고-하더라도-맥락에-따라-해석이-달라질-수-있다">이 문제가 어려운 이유는 자연어 자체가 굉장히 복잡하고 이해하기 어려우며 맥락 의존적이기 때문이다. 같은 말이라도 다양한 의미가 있고 같은 의미라고 하더라도 맥락에 따라 해석이 달라질 수 있다.</h4>
<h4 id="과거-사용된-방법은-wordnet과-같은-동의어-사전을-활용하는-방식이다-하지만-결국-사전식에-의존한-방식은-모든-맥락을-다루지-못할-뿐더러-신조어에-대처할-수-없다">과거 사용된 방법은 wordnet과 같은 동의어 사전을 활용하는 방식이다. 하지만 결국 사전식에 의존한 방식은 모든 맥락을 다루지 못할 뿐더러, 신조어에 대처할 수 없다.</h4>
<h4 id="또-다른-방식으로-원핫-인코딩을-생각해볼-수-있다-각-단어의-개별성을-인정하는-방식으로-원핫인코딩을-하는-것인데-이때-문제는-단어별-유사성과-맥락을-전혀다루지-못한다는-것이다-왜냐면-원핫-인코딩은-각-벡터간-orthogonal-하기-때문이다">또 다른 방식으로 원핫 인코딩을 생각해볼 수 있다. 각 단어의 개별성을 인정하는 방식으로 원핫인코딩을 하는 것인데 이때 문제는 단어별 유사성과 맥락을 전혀다루지 못한다는 것이다. 왜냐면 원핫 인코딩은 각 벡터간 orthogonal 하기 때문이다.</h4>
<h4 id="어떤-자연어를-벡터화-한다고-했을-때-좋은-벡터는-결국-유사한-의미를-지니고-있는-또는-비슷한-맥락에서-쓰이는-벡터들-간-유사성을-보이는-성질을-가지고-있을-것이다-가령-모텔과-호텔이라는-단어가-있을-때-둘은-분명-다른-말이지만-굉장히-유사한-의미와-맥락을-지니므로-각-단어를-벡터화-했을-때-벡터간-유사성이-높을-때-잘-벡터화했다고-볼-수-있다">어떤 자연어를 벡터화 한다고 했을 때, 좋은 벡터는 결국 유사한 의미를 지니고 있는 또는 비슷한 맥락에서 쓰이는 벡터들 간 유사성을 보이는 성질을 가지고 있을 것이다. 가령 모텔과 호텔이라는 단어가 있을 때 둘은 분명 다른 말이지만 굉장히 유사한 의미와 맥락을 지니므로 각 단어를 벡터화 했을 때, 벡터간 유사성이 높을 때 잘 벡터화했다고 볼 수 있다.</h4>
<h4 id="이렇게-자연어를-바라보는-것이-distributional-semantics-관점이다-해당-관점에서는-자연어의-의미는-그것의-맥락에-의해서-결정된다고-바라본다">이렇게 자연어를 바라보는 것이 distributional semantics 관점이다. 해당 관점에서는 자연어의 의미는 그것의 맥락에 의해서 결정된다고 바라본다.</h4>
<blockquote>
<p>You shall know a word by the company it keeps” 
(J. R. Firth 1957: 11)</p>
</blockquote>
<h4 id="아래-사진에서-banking의-의미를-어떻게-해석할-것인가는-그-단어-자체가-본질적으로-가지고-있는게-아니라-주변-맥락-단어들에-의해서-결정된다">아래 사진에서 banking의 의미를 어떻게 해석할 것인가는 그 단어 자체가 본질적으로 가지고 있는게 아니라 주변 맥락 단어들에 의해서 결정된다.</h4>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/7d1e2921-2ce7-4a4e-9a7f-516a93b5ce1c/image.jpeg" alt=""></p>
<h4 id="이러한-맥락을-반영해서-아래와-같이-8차원으로-벡터화-해볼-수-있다">이러한 맥락을 반영해서 아래와 같이 8차원으로 벡터화 해볼 수 있다.</h4>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/338e8607-0132-4cc4-a76f-37387e329f05/image.jpeg" alt=""></p>
<h4 id="이런-방식으로-비슷하게-벡터를-2차원으로-벡터화했다고-가정해보자-이때-2차원-평면상에-벡터들을-그린다고-하면-아래와-같이-표현될-수-있다">이런 방식으로 비슷하게 벡터를 2차원으로 벡터화했다고 가정해보자. 이때 2차원 평면상에 벡터들을 그린다고 하면 아래와 같이 표현될 수 있다.</h4>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/158b2ca3-97f5-4725-a8cc-6b9582c8208a/image.jpeg" alt=""></p>
<h4 id="이때-need-help은-의미가-비슷하고-같은-맥락을-공유하므로-가깝게-위치한다-비슷하게-being-been도-had-has-have도-가깝게-위치한다-위치가-가깝다는-것-자체가-벡터-상-유사도가-높다는-것을-의미하기에-이-경우-벡터화가-잘-되었다고-볼-수-있다-물론-실제-벡터는-이보다-훨씬-높은-256512-차원-정도의-벡터를-사용한다">이때 need, help은 의미가 비슷하고 같은 맥락을 공유하므로 가깝게 위치한다. 비슷하게 being, been도, had, has, have도 가깝게 위치한다. 위치가 가깝다는 것 자체가 벡터 상 유사도가 높다는 것을 의미하기에 이 경우 벡터화가 잘 되었다고 볼 수 있다. 물론 실제 벡터는 이보다 훨씬 높은 256,512 차원 정도의 벡터를 사용한다.</h4>
<h4 id="앞으로-다룰-내용은-어떻게-이런-벡터를-얻어내는-가에-대한-얘기이다-다음-글-부터-전통적-통계적-기법에서부터-bag-of-words-관점에서의-skip-gram-cbow-glove-fasttext-그리고-더-나아가-최근의-large-language-model에서의-벡터화-방법까지-다뤄보고자-한다">앞으로 다룰 내용은 어떻게 이런 벡터를 얻어내는 가에 대한 얘기이다. 다음 글 부터 전통적 통계적 기법에서부터 bag of words 관점에서의 skip-gram, cbow, glove, fasttext 그리고 더 나아가 최근의 large language model에서의 벡터화 방법까지 다뤄보고자 한다.</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[﻿내가 기억하고 싶어서 기록하는 가상환경 커맨드]]></title>
            <link>https://velog.io/@o_o_gie/%EB%82%B4%EA%B0%80-%EA%B8%B0%EC%96%B5%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4%EC%84%9C-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD-%EC%BB%A4%EB%A7%A8%EB%93%9C</link>
            <guid>https://velog.io/@o_o_gie/%EB%82%B4%EA%B0%80-%EA%B8%B0%EC%96%B5%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%96%B4%EC%84%9C-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD-%EC%BB%A4%EB%A7%A8%EB%93%9C</guid>
            <pubDate>Wed, 15 Jun 2022 06:39:49 GMT</pubDate>
            <description><![CDATA[<p>오늘은 가상환경을 공부한 김에 아카이브할 겸 가상환경이 뭔지 어떻게 쓰는건지 써보고자 한다.</p>
<h3 id="가상환경이란-무엇인가-왜-필요한가">가상환경이란 무엇인가? 왜 필요한가?</h3>
<p>데이터 사이언스를 포함한 다양한 개발을 하다보면 프로그램을 사용해야함은 당연하다.</p>
<p>나 같은 경우 딥러닝을 공부하다보면 python, pytorch, tensorflow, numpy, matplotlib 기타 등등 다양한</p>
<p>라이브러리를 사용하는데 이 패키지들은 버전이 지속적으로 업데이트 된다. 이때 버전에 따라서 사용문법이 달라질 수 있다는 점이 중요하다. 가령 파이썬 3.6에 익숙한 사람이 있고 3.9에 익숙한 사람이 있다면 각자에게 맞는</p>
<p>파이썬을 사용해야 한다. 이때 하나의 버전만 사용하지 않고 특정 버전의 패키지를 모아서 사용가능한데</p>
<p>이를 가상환경이라 한다. 가령 나는 딥러닝을 수행 할때 파이썬 3.9 , 파이토치 1.10 버전을 사용한다면 이 버전의 패키지를 모아서 환경을 구성한다. 만약 딥러닝이 아니라 머신러닝 또는 다른 과제를 수행한다면 특정</p>
<p>라이브러리를 빼고 다른 라이브러리로 구성된 가상 환경을 만들어서 그들을 사용할 수 있다.</p>
<p>사실 이게 가장 중요해지는 지점은 함께 프로젝트를 수행할 때이다. 서로 다른 환경에서 작업한 파일을 공유해서</p>
<p>프로젝트를 수행한다는 것은 말이 안되는 행동이다. 그래서 함께 사용할 라이브러리를 지정하여 가상환경을 구성한 뒤 그 환경에서만 해당 프로젝트를 진행한다. 이것이 가상환경이 정말 중요한 이유이다.</p>
<h3 id="가상환경-커맨드">가상환경 커맨드</h3>
<p>내가 이제부터 설명할 가상환경의 구축은 anaconda(데싸를 하면 대부분 anaconda를 처음 사용하게 될 것이다)를 기준으로 설명할 것이다. 가상 환경에서 사용되는 커맨드는 anaconda prompt와 컴퓨터의 명령 프로토콜(cmd)을 이용할 수 있는데 anaconda prompt를 기준으로 작성하겠다. 장점이 있다고 생각하기 때문인데 이것은 이후에 얘기하겠다.</p>
<h3 id="conda-env-list">conda env list</h3>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/11d26b2c-13c4-478d-823c-96752b3dfa0e/image.png" alt=""></p>
<p>이 커맨드는 현재 나의 컴퓨터에 어떤 가상환경이 있는지 확인하는 커맨드이다. 대부분의 가상환경 커맨드는</p>
<p>conda env로 시작한다. 결과창에 base라는 가장 기본의 환경이 주어지고 이후 추가로 환경이 있다면 아래 나온다.</p>
<p>현재는 base말고 환경이 없는 상태이다.</p>
<p>만약 커맨드를 사용하지 않고 Gui로 접근하고 싶다면 설치되어있는 anaconda navigator를 실행하고 environments를 눌러주면 본인의 가상환경을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/cd18a598-38e6-4335-a0f5-66893a458c03/image.png" alt=""></p>
<p>위에서 현재 내 환경에 설치된 라이브러리와 그 버전을 확인할 수 있으며 새롭게 설치하거나 업데이트할 수 있다.</p>
<h3 id="conda-env-create--n-환경명">conda env create -n 환경명</h3>
<p>이 명령어는 새로운 환경을 만들어주는데 개인적으로 새로운 환경을 만들때는 커맨드보다 네비게이터를 활용하길 바란다. 그 이유는 버전 선택의 
편의성 때문인데, 네비게이터로 환경을 만든다면</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/9a0932bc-d548-4716-bdf5-e9fdc8d2865a/image.png" alt=""></p>
<p>위와 같이 파이썬과 R을 고를 수도 있고 버전 설정도 쉽다. 물론 커맨드로도 버전을 설정할 수 있다.</p>
<p>환경이름 옆에 python= 3.9.4 이런식으로 쓰면되지만 그냥 네비게이터가 편하니까 추천한다.</p>
<h3 id="activate-환경명">activate 환경명</h3>
<p>내가 원하는 환경을 설정하고 싶다면 이 명령어를 입력해야한다. 내가 원하는 환경명을 입력하면 해당 환경이 설정된 것이다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/0d9a9e01-5ed2-4f5d-bf1e-20cd71c9be41/image.png" alt=""></p>
<p>oogie torch라는 환경을 만들었고 그 환경으로 활성화가 된 것을 확인할 수 있다. 이제부터 입력하는 명령어 및 작업은 다 저 환경에서 이루어진다. 이를 비활성화하고싶다면 conda deactivate를 입력해주면 된다.</p>
<h3 id="conda-install-packagename">conda install packagename</h3>
<p>환경을 만들었을 때 파이썬과 정말 필요한 소수의 패키지만 설치되었을 테니 이제 필요한 패키지를 설치 해야한다.위의 커맨드는 내가 원하는 패키지 이름을 설치하는 커맨드이다.</p>
<p>반면 명령 프롬프트로 진행할 때는 pip install 패키지 이름을 입력하는 식으로 패키지를 설치할 수 있는데,</p>
<p>pip install은 별 생각없이 최신버전의 패키지를 깔아버린다. 이때 호환성 문제가 발생할 수 있는데</p>
<p>conda install은 알아서 호환성을 고려하여 패키지의 버전과 환경을 맞춰준다.</p>
<p>앞서 conda prompt를 추천한 이유가 이것이다. 커맨드로 설치할 때는 conda install을 사용하길 추천한다.</p>
<p>물론 커맨드말고 네비게이터로 설치할 수도 있고 이것도 정말 정말 편하다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/f423fbd0-93c3-4c6d-a329-2649d057b762/image.png" alt=""></p>
<p>새로 만든 환경엔 pytorch가 없어서 uninstall의 목록에서 검색했을 때 나온다. 원하는 버전에 맞춰 설치하면 된다.</p>
<h3 id="conda-create--n-새로운-환경이름-clone-복사할-환경이름">conda create -n 새로운 환경이름 —clone 복사할 환경이름</h3>
<p>만약 기존에 있는 환경을 약간 수정하여 새로 쓰고싶다면 위의 커맨드로 기존 환경을 복사하여 환경을 만들 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/928d1aab-1557-41b0-b4b4-368d95a13850/image.png" alt=""></p>
<h3 id="conda-remove--n-환경이름---all">conda remove -n 환경이름 --all</h3>
<p>이부분도 중요한데 내가 필요하지 않은 환경을 지우는 커맨드다. 로컬 환경이 좋더라도 불필요한 환경을 여러개 두는 것은 좋지 않으므로 가급적 사용하지 않는 환경은 빠르게 지워주는게 좋다. 물론 네비게이터로도 지울 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/6deb9f8a-0362-44db-aeae-540be553c346/image.png" alt=""></p>
<p>다 지워진 것처럼 보이지만 가상환경 폴더에 남아있는 잔여패키지가 있을 것이다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/d169bf25-b1f5-4e3d-b7ef-9bdd18769e6b/image.png" alt=""></p>
<p>이 파일 경로는 아나콘다에서 가상환경의 패키지를 저장한 곳인데 test가 지워지지 않았고 내부에 잔여파일을 확인할 수 있는데 여기까지 지워주면 
완벽하게 지우는 것이다. 참고로 지울때 커맨드나 네비게이터가 켜져있으면 안꺼지니까 끄고 지워야한다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/186d3e03-086e-448e-aa9e-2647b76f3873/image.png" alt=""></p>
<p>위의 경로는 현재 존재하는 가상환경의 경로를 기록하는 txt파일의 위치이다. 혹시 경로를 놓쳤다면 참고하면 된다.</p>
<h3 id="conda-env-export--envyaml">conda env export &gt; env.yaml</h3>
<p>굉장히 중요한 커맨드인데 내가 쓰고 있는 가상환경의 패키지와 버전을 env.yaml파일로 만들어준다.</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/22f90e59-f03c-43ea-9ed1-2813e90334c1/image.png" alt=""></p>
<p>파일이 만들어졌다면 확인하면 되는데 나는 코드를 편집할 때 vscode를 사용하기 때문에 vscode로 열어보면</p>
<p><img src="https://velog.velcdn.com/images/o_o_gie/post/e75a9082-bcfb-4c41-9227-6e25bcbc7935/image.png" alt=""></p>
<p>다음과 같이 환경에 대한 정보가 나온다. 사실 나 혼자 작업할 때는 이 파일이 필요없다.</p>
<p>근데 애초에 가상환경이란게 협업에서 중요하듯 위의 파일도 협업에서 필수이다.</p>
<p>상대방의 가상환경을 일일히 따라서 설치하고 만들 수 있지만 위의 파일을 받는다면 해당 파일을</p>
<p>내 가상파일에 대한 정보가 담긴 경로에 두고 conda env create -f 파일명 -n 환경이름 커맨드를 입력하면</p>
<p>파일에 적힌 정보와 똑같은 가상환경을 구축해준다. 간단한 커맨드지만 협업에서 필수적이고 사실 가장 핵심이되는 커맨드이다.</p>
<p>이번에는 가상환경에 대해서 다뤄봤는데 협업이나 프로젝트를 이미 하고있는 분들이라면 잘 쓰고 있겠지만</p>
<p>로컬이 아닌 코랩만으로 프로젝트를 해왔거나 협업을 한적이 없는 분들에게 도움이 되었으면 한다.</p>
<h3 id="요약">요약</h3>
<p>1.코랩이 아닌 로컬에서 협업시 가상환경은 필수이다.</p>
<p>2.아나콘다 프롬프트나 네비게이터를 사용하면 편하게 구축가능하다</p>
<p>3.불필요한 환경은 지우자</p>
<p>4.conda env export로 환경을 공유하자</p>
]]></description>
        </item>
    </channel>
</rss>