Backstage Python

신경망의 눈으로 Python 보기

Read this post in other languages:
English, Français, 简体中文

JetBrains의 Python용 전체 줄 코드 완성 플러그인이 이제 공개 베타로 제공됩니다. 이 플러그인을 만드는 데 사용된 몇 가지 기술과 알고리즘에 대해 이야기하고 그 과정에서 수집한 Python 프로그래밍에 대한 통계 정보를 공유하려고 합니다.

‘전체 줄 코드 완성’이란 무엇일까요?

입력하려는 다음 단어를 제안하는 코드 완성에는 이미 익숙하실 것입니다. 그렇지 않다면, 이 내용을 다룬 일련의 자료를 확인해 보세요(1, 2, 3, 4).

전체 줄 코드 완성은 더 큰 코드 조각을 제안하여 이 기능을 더욱 확장합니다. 무엇보다도 사용자 대신 메서드 호출 매개변수를 입력하거나 오류 메시지 텍스트를 작성해줍니다. UX 측면에서 일반 코드 완성과 유사하며 동일한 팝업을 사용하여 결과를 표시합니다.

이 이미지는 코드 완성 팝업이 표시된 코드 조각을 보여줍니다. 팝업에는 전체 줄 마크가 있는 세 개의 제안이 포함되어 있습니다. 각 제안에는 여러 매개변수가 있는 메서드 호출이 포함됩니다.

그러나 이 확장된 완성 형식의 이면에 있는 기술은 근본적으로 다릅니다.

표준 코드 완성은 정적 분석을 사용하여 현재 위치에서 볼 수 있는 클래스, 메서드, 필드 변수 및 키워드를 파악한 다음 머신러닝을 적용하여 가장 좋은 제안을 선택합니다. 사전에 파악된 집합에서 최선의 항목을 선택하는 것은 분별 모델링(discriminative modeling)의 예입니다. 반면에 전체 줄 완성은 여러 ‘단어’로 구성된 새로운 코드 청크를 제공합니다. 코드 작성이 창의적인 과정처럼 보이며 더 계산 집약적입니다. 이는 생성 모델링(generative modeling)의 예입니다. 신경망은 우리가 사용하는 또 다른 일반적인 생성 모델링 도구입니다.

그러면 전체 줄 코드 완성의 이면에서 일어나는 일을 자세히 소개하고 이 기능의 작동 방식에 대한 몇 가지 흥미로운 측면을 보여 드겠습니다. 오늘의 초점은 ‘신경망은 프로그램을 어떤 시각으로 보는가?’입니다.

프로그래밍 언어로 작동하는 알고리즘은 자연어 처리의 다양한 기법을 사용합니다. 원래 언어의 단어를 처리하는 대신, 신경망은 특수 어휘에서 가져온 토큰 시퀀스를 이용해 작동합니다. 알고리즘은 바이트 페어 인코딩 프로세스를 사용하여 어휘를 구축합니다. 먼저 간단한 영어로 된 예시를 살펴본 다음 이것이 Python에서 어떻게 작동하는지 살펴보겠습니다.

자연어의 바이트 페어 인코딩

알고리즘은 학습 단계를 시작할 때 어휘를 구축하고 학습에 사용된 텍스트로부터 통계를 수집합니다. 토큰 세트는 그 이후에 변경되지 않으며, 활용 단계 동안 어떤 처리도 수행되지 않도록 고정된 상태로 유지됩니다.

학습에 사용할 수 있는 영어가 다음 문구밖에 없다고 가정해 보겠습니다.

이 이미지에는 다음 문구가 표시됩니다. if you always do what you always did you will always get what you always got

이 옛 지혜는 여전히 진실일 수도, 그렇지 않을 수도 있습니다. 진실 여부와 관계없이, 여기에는 반복되는 단어가 여러 개 포함되어 있어 알고리즘을 이해하는 데 도움이 됩니다. 추가 사용을 위해 여기에서 어떤 종류의 토큰을 얻을 수 있을까요? 학습 문장에 있는 모든 단일 문자(기호)로 토큰 목록을 초기화합니다.

[i, f, y, o, u, a, l, w, s, d, h, t, g, e]

참고로, 유니코드는 각 문자를 단일 바이트로 코딩하므로 여기서 기호와 바이트는 동일합니다.

공백을 포함하지 않는 대신 이를 자연적인 토큰 나누기로 사용할 수 있습니다. 이 내용은 나중에 중요해질 겁니다.

초기화 후에는 어휘를 반복적으로 확장합니다. 기존 두 항목의 가장 일반적인 연결형을 찾아 이를 새 토큰이라고 부릅니다. 이 예에서 “you”와 “always”는 네 번 반복되지만 어느 것도 항목의 연결형이 아니기 때문에 아직 추가할 수 없습니다. 단계별로 진행해보겠습니다. 첫 번째 후보는 “yo”입니다. 새 토큰이 정의되면 이 토큰은 분해할 수 없으며 각 부분을 개별적으로 사용할 수 없습니다. 따라서 “ou”는 “yo”를 분할해야 만들 수 있으므로 토큰이 되지 않습니다.

이 애니메이션 이미지는 이전 이미지의 문구에 적용되는 바이트 쌍 분할의 여러 단계를 보여줍니다. if you always do what you always did you will always get what you always got 이 이미지에 표시된 토큰은 다음과 같습니다. yo, ys, al, ays, ways, always, you, wt, at, what

다른 빈번한 토큰 연결형을 찾아 어휘에 추가하면서 이 단계를 반복합니다. 알고리즘은 두 번만 나타나는 “wh” 및 “at”과 같은 다른 기호 페어를 처리하기 전에 “you” 및 “always”에 도달할 것입니다. 크기 32(편리한 2의 거듭제곱)의 어휘는 다음과 같을 것입니다.

[i, f, y, o, u, a, l, w, s, d, h, t, g, e, yo, ys, al, ays, ways, you, always, at, wh, what, if, il, id, ot, wil, do, did, ge]

우리가 추가하는 모든 토큰은 두 개의 다른 토큰을 연결한 것이므로 이를 페어 인코딩이라고 합니다.

이제 토큰이 무엇인지에 대한 이해의 차이로 생기는 잠재적인 혼동을 명확히 짚고 넘어갈 때이군요. 언어의 어휘적 요소를 ‘단어’라고 하고, 바이트 페어 인코딩을 통해 얻은 어휘의 항목을 ‘토큰’이라고 하겠습니다. 단어가 아닌 토큰이 있을 수 있습니다(예: ays). 토큰이 아닌 단어도 있을 수 있습니다(예: will 및 get. 이 항목을 포함하기 전에 어휘 생성을 중단함).

신경망은 입력된 내용을 취하여, 단어가 아니라 토큰으로 결과물을 생성합니다. 필요한 계산 리소스는 어휘의 규모에 따라 다릅니다. 이 기법을 사용하면 모든 언어 단어를 사용하는 대신 원하는 값으로 크기를 제한할 수 있습니다.

입력에 포함된 토큰의 양도 품질과 성능에 매우 중요합니다. 알고리즘은 연속성을 생성해야 하는 텍스트의 조각(“컨텍스트”라고 함)만 고려할 수 있습니다. 더 많은 확장 토큰을 사용하면 더 큰 텍스트 청크를 입력으로 사용할 수 있습니다. 다행히 프로그래밍 언어로 작업할 때 이를 위한 추가 옵션이 제공됩니다.

자연어에서 프로그래밍 언어로

자연어 처리 시스템의 작성자는 일반적으로 단어 경계를 토큰 간의 확실한 경계로 사용합니다. “John Smith” 시퀀스는, 특히 논픽션을 고려하는 경우 “Trantor”보다 영어에서 더 자주 사용됩니다. 그러나 “Trantor”는 단일어이기 때문에 이론적으로 상당히 많은 어휘에서 토큰이 될 기회가 있습니다. 두 단어로 된 “John Smith”에는 그러한 기회가 없습니다. 자연어의 토큰은 일반적으로 단어 경계를 넘지 않습니다.

프로그래밍 언어의 경우 렉서에서 토큰을 자연스럽게 분리합니다(단어는 공백을 삽입할 수 있는 위치에서 끝남). 변수, 상수 및 언어 키워드는 분리된 상태로 유지됩니다.

프로그래밍 언어라면 우리가 더 잘 할 수 있을 것이라고 생각했습니다. 다음의 일반적인 Python 구문을 살펴보겠습니다.

for i in range(

여기에는 두 개의 언어 키워드, 변수 및 함수를 포함하여 다양한 어휘 요소가 포함되어 있습니다. 그래도 제 안에 있는 (매우 미숙한) Python 프로그래머는 for i in range(가 어휘의 단일 항목이어야 한다고 생각합니다. 신경망은 예측을 위해 4~5회가 아닌 1회의 반복 추론을 실행해야 하므로 중괄호 내에 있는 내용에 소비되는 노트북의 컴퓨팅 용량을 절약할 수 있습니다.

여기서 아이디어는 어휘 요소 대신 줄바꿈으로 토큰을 제한하는 것입니다. 우리의 목표는 전체 줄 코드 완성이므로 전체 줄을 최대 토큰으로 사용하는 것이 자연스럽습니다.

여기서 어휘 크기 제한은 16,384이며, 편리하게도 다시 2의 거듭제곱입니다. 텍스트의 99.99%를 차지하는 유니코드 문자로 프로세스를 초기화하고 허용 라이선스로 Python 저장소의 처리를 시작했습니다.

다음 섹션에서는 가장 인기 있는 16,384개의 Python 구성에 대해 알아봅니다.

어휘의 문제

우리는 어휘를 시각적으로 검사하여 몇 가지 구조적 문제를 발견했습니다. 통계에 따르면 일부 항목이 있어야 하지만 이들 항목을 포함하면 몇 가지 단점이 생깁니다.

첫 번째 복병: 중국어

바이트 이후에 나타난 600개 정도의 토큰은 대부분 한자와 같은 인기 있는 비라틴 문자 언어의 유니코드 기호였습니다. 중국어 자체가 복병이었습니다. 그것 말고는 문자의 의미는 합리적으로 보입니다.

예를 들어, 가장 자주 나타나는 항목은 입니다. 이 기호는 흔히 영어 “s”와 유사하게 사용되고 소유격을 나타내며, 통계적으로 가장 많이 사용되는 한자입니다. 일부는 데이터세트에서 매우 많이 사용되었지만 언어 자체에서는 덜 자주 사용되었습니다. 예를 들어, 은 숫자와 관련되고 은 ‘네트워크’를 의미합니다. 프로그래밍 컨텍스트를 감안할 때 여기에서 이상한 점은 없습니다. 이러한 기호가 어휘에 나타난 이유만 알아내면 됩니다.

중국어, 러시아어, 일본어, 한국어, 때로는 아랍어를 포함한 비라틴 문자는 Python docstring에서 가져옵니다. 놀랍게도 사람들은 종종 모국어로 문서를 작성합니다.

비라틴 문자를 어휘에 포함하면 프로그래밍 언어의 더 인기 있는 구문으로 더 효과적으로 사용할 수 있는 공간을 해당 문자가 차지하므로 이점보다 단점이 더 많을 수 있습니다. 이러한 문자를 사용하면 Python에서 허용되는 유니코드 변수 이름을 더 잘 지원할 수 있습니다. 그러나 사람들은 이를 거의 사용하지 않으며 주로 코드 난독화에 사용합니다.

두 번째 복병: 들여쓰기

return과 같이 줄의 시작 부분에서 많이 사용되는 키워드에 대한 어휘를 검색하면 또 다른 반갑지 않은 현상이 나타납니다.

return
        return
    return
                return
return

(그리고 자주 나타나는 다른 항목)

중요한 return 문 조각이 선행하는 온갖 들여쓰기와 함께 나타나므로 어휘에서 다음 토큰의 여러 복사본을 얻게 됩니다.

return self.
return True
return False
return 0
return None

이러한 복사본을 유지하는 것은 실용적이지 않은 것처럼 보일 수 있습니다. 코드 완성을 호출할 때 캐럿이 줄의 시작 부분에 있는 경우는 거의 없습니다. IDE는 들여쓰기를 추적하므로, 사용자는 이미 올바른 위치에서 return을 입력하기 시작하게 됩니다. 따라서 선행 공백이 있는 return 문이 사용될 가능성은 무시할 수 있습니다.

다른 한편으로, 컨텍스트의 길이가 절약됩니다. 들여쓰기된 모든 return은 여러 개가 아닌 하나의 토큰일 것입니다.

Import 문

우리를 고민하게 만든 또 다른 문제는 TensorFlow, PyTorch 및 Django와 같은 주요 프레임워크와 관련된 import 문이었습니다. 예를 들면 다음과 같습니다.

from tensorflow.python.framework import ops
from django.conf import settings

이러한 import 문은 일반적으로 파일당 한 번 나타나며 시작 부분에 가까이 있는 것이 보통입니다. 또한 보통의 코드 완성 및 자동 가져오기 메커니즘으로도 목적을 충분히 달성할 수 있습니다. 한편, 이번에도 코드 완성 제안을 생성할 때 더 큰 코드 청크를 고려할 수 있습니다. 이러한 긴 import 문이 토큰으로 필요한지 여부에 대해서는 여전히 생각해볼 문제입니다.

데이터 살펴보기

이 섹션의 핵심 요지는 데이터의 시각적 검사가 갖는 중요성일 것입니다. 통계 데이터 처리 및 ML 기반 알고리즘에서 나온 중간 결과에는, 테스트에서는 무시되지만 인간 전문가에게는 바로 지적되는 시스템 문제가 포함될 수 있습니다.

통계 + 경험적 지식이 통계보다 훨씬 나은 경우가 많습니다.

자주 사용되는 기호 쌍

아래로 스크롤하면 마침내 2개의 기호 조합이 보입니다. 이 조합이 목록의 맨 위에 오른 이유를 알아보는 일은 매우 흥미롭습니다. 어떤 조합은 가장 일반적인 언어 키워드에서 비롯된 반면, 어떤 조합은 명명 규칙 및 프로그래밍 습관이 만들어낸 결과입니다.

Python에서 가장 빈번하게 나타나는 2개의 기호 조합이 뭔지 예상되시나요(물론 이중 공백은 제외하고)? 바로 쉼표+공백입니다(예: , ). 납득이 가는 결과죠! 다음 내용을 살펴보겠습니다.

se는 주로 self.에서 파생됩니다. 사실, self.는 가장 빈번한 5개 기호 조합이며 맨 위에 잘 오르는 토큰입니다. 이 조합은 그 안에 포함된 시퀀스를 제외하면 대부분의 2개 기호 및 모든 3개 기호 시퀀스보다 상위를 차지합니다.
in Yay! for i in range(
re는 대부분 return에서 파생됩니다.
on은 흥미로운 사례입니다. 많은 변수, 필드, 심지어 클래스 이름이 “tion”, “cion” 또는 “sion”으로 끝납니다. 그 외에 중요한 두 가지로 “None”과 “json”이 있습니다.
te는 처음에는 파악이 되지 않지만 어휘를 더 살펴보면 “test”라는 단어가 다양한 조합으로 많이 쓰인다는 것을 알 수 있습니다. “date,” “text,” “state,” “write” 및 어디서나 볼 수 있는 “items”와 같은 단어도 이 토큰의 빈도를 높입니다.
사실 도 많이 보입니다. 하지만 순서를 바꿨을 때  =는 그만큼 빈번히 나타나지 않습니다. 그 빈도 수의 차이는 != 연산자의 빈도 수보다 확실히 큽니다.
oror, forimport와 같은 주요 언어 키워드에서 대부분 파생됩니다.

복잡한 기호 조합

경계를 넘는 토큰

꽤 인기 있는 두 가지 토큰인 s[  및 s.append(는 Python에서 목록이 어떻게 사용되는지 완벽하게 설명해줍니다. 일반적으로 목록의 이름에는 days, goods, items, lines와 같은 복수형 명사가 사용됩니다. 이러한 토큰은 통계의 힘과 가치를 잘 보여줍니다. 코드에서 영어 구사 패턴을 표현하기 위해 Python 어휘 요소의 경계를 넘기 때문입니다.

range 문

전체 줄 토큰을 소개하며 언급했던 range 문으로 돌아가 보겠습니다. 이 단어가 어휘에 들어 있나요? 사실 이 단어는 다음에서 볼 수 있습니다.

for i in range(

또한 많이 이용되는 더 긴 문장이 있습니다.

for i in range(len(

참고로, i는 잘 알려진 유일한 카운터 이름입니다. j 및 k와 같은 다른 이름은 상위에 오르지 못합니다.

Return 문

프로그래머들은 함수의 결과로 무엇을 사용할까요? 다음의 빈도는 놀랄 일이 아닙니다.

return False
return True
return None

이 세 항목의 빈도는 서로 비슷하지만 모두 return self보다 아래입니다. 일반적으로 사용되는 또 하나의 토큰은 return 0입니다. 1, 2 또는 다른 숫자는 통계에서 볼 수 있을 만큼 자주 사용되지 않습니다. 그 외에 흥미로운 단어는 다음과 같습니다.

return []
return super(
return data
return value
return not
return len(self.

return len(는 사전에 없기 때문에 마지막 토큰은 return  및 len(self.를 결합하여 얻어집니다.

클래스

class 단어와 조합된 단어 중에 상위 16,384개에 들어갈 만큼 인기 있는 것이 있을까요? 단연 눈에 띄는 상위 단어는 class Test로, 순위가 매우 높습니다. 같은 형태의 조합이지만 다음 두 조합은 이보다 많이 뒤처져 있습니다.

class Meta:
class Base

class를 포함하는 다른 의미 있는 토큰은 클래스 이름이 아닙니다.

classifier
classification
issubclass(
@classmethod
Base class for

마지막 두 가지는 문서에서 가져온 것입니다.

결론

신경망은 어휘를 사용하여 입력 프로그램을 나타내고 제안을 구성합니다. 코드 생성 알고리즘은 향후 다른 자료에서 주제로 다룰 예정이며, 프로그램 표현은 현재 사용할 수 있습니다.

Python 코드의 간단한 조각을 가져와 토크나이저를 실행하면 신경망이 하는 동일한 결과를 볼 수 있습니다.

이 이미지는 세 줄의 Python 코드를 보여줍니다. 이러한 줄은 15개의 토큰으로 분할됩니다.

토크나이저는 이 3개의 줄을 15개의 토큰으로 변환합니다. GPT-2 기반 모델은 최대 1,024개 항목을 사용할 수 있습니다. 우리 모델은 최대 384개를 사용합니다. 한 줄의 중앙값 길이는 우리 학습 세트에서 토큰 10개로, 위의 예보다 약간 더 깁니다. 해당 모델은 예측을 생성하기 위해 40줄 미만의 컨텍스트를 사용한다고 추정할 수 있습니다.

이 예는 또한 신경망의 ‘사고 방식’이 인간과 얼마나 다른지 보여줍니다. 우리는 첫 번째 줄과 두 번째 줄의 args가 동일하다고 이해합니다. 그러나 알고리즘은 두 개를 다르게 보고, 심지어 두 번째 단어는 분해합니다. 이는 설계에서 의도된 것입니다.

Python용 전체 줄 코드 완성 플러그인 사용해보기

토큰 어휘를 탐색하는 것은 매우 재미있는 일지만, 이를 개발하는 것은 더 높은 수준의 제품 목표를 향해 나아가는 하나의 단계일 뿐이었습니다. 신경망이 코드를 생성하는 방법, 그리고 사용자의 랩톱 사양에 맞게 그 성능을 조정하는 방법에 대한 기술적인 이야기를 앞으로 더 많이 소개할 계획입니다.

그동안 여러분은 저희의 작업 결과물인 Python용 전체 줄 코드 완성 플러그인을 사용해보시기 바랍니다.

https://plugins.jetbrains.com/plugin/14823-full-line-code-completion

게시물 원문 작성자

Jessie Cho

Roman Poborchiy