Hong의 모든 글

워드프레스 구텐베르크에서 Mermaid 다이어그램 그리기

graph LR M --> e e --> r r --> m m --> a a --> i i --> d

위와 같은 다이어그램을 그리는 R패키지중에 DiagrammeR라는 것을 소개드리면서 Mermaid에 대한 설명을 잠깐 했었습니다.  

지난 포스트 링크입니다.
DiagrammeR – R 다이어그램 그리기

워드프레스 구텐베르크 편집기를 사용할 때 조금 손이 가지만 그래도 워드프레스의 설정에서 고난의 삽질을 하지 않고 간단하게 Mermaid를 사용하는 방법을 설명드리려고 합니다. 데이터사이언스와 관련없는 포스트는 이 블로그에는 잘 안쓰지만 일탈을 해봤습니다.

구텐베르크 Gutenberg

Mermaid를 워드프레스(WordPress)에서 사용하는 방법을 설명하기전에 구텐베르크 얘기를 먼저 해야 하겠습니다.

워드프레스가 글 편집기로 구텐베르크(Gutenberg)라는 것을 쓰도록 유도한지가 꽤 된 것 같습니다. 이 포스트를 쓰는 시점으로부터 수개월 전으로 기억합니다. 원래 구텐베르크가 워드프레스에 기본 패키지에 포함되어 있지는 않는 것 같은데  “편집기로 구텐베르크 써보지 않겠어?” 라는 식으로 관리자화면에서 자꾸 물어보길에 예전에 무심결에 눌러서 플러그인을 설치한 후에 제 블로그도 구텐베르크로 작성하고 있습니다. 그 뒤로는 전 너무 멀리 와버렸습니다. ‘ㅁ’;

구텐베르크는 포스트를 쓰면서 블럭 단위로 콘텐트를 나눠서 관리하고 편집하는 방식인데 위지윅(WYSIWYG)에 중점을 더 둔 편집방식입니다. 물론 그전 방식인 클래식 에디터를 같이 쓸 수 있습니다만 클래식 편집기로 작성한 글은 클래식편집기로만 열리고 구텐베르크로 작성한 것은 구텐베르크 편집기로 열립니다. 

구텐베르크 편집기를 쓰면 편리한 점이 더 많아서 저는 구텐베르크를 주로 쓰고 있습니다. 구텐베르크라는 이름처럼 출판 편집을 위한 소프트웨어를 사용하는 기분이 듭니다.

구텐베르크는 사용하려면 적응기간이 조금 필요합니다. 버그도 아직 꽤 있습니다. 복잡한 구조의 포스팅을 안하시는 분들께는 좋겠고 복잡한 요소들이 잔뜩 있는 포스트를 장벽이 큽니다.

기존에 잘 쓰던 플러그인과 충돌을 하거나 작동이 안되는 부분도 꽤 많아서 플러그인을 별도로 찾아서 추가 설치해야 하거나 포기해야 하는 부분도 상당합니다. 그래서 클래식 편집기에서는 플러그인만 설치하거나 익숙하게 했던 작업도 구텐베르크를 쓰면서 부터는 많이 힘들어졌습니다.

워드프레스에 Mermaid.js 추가하기

워드프레스에 Mermaid.js를 사용하려면 Mermaid.js와 CSS하나만 추가해주면 됩니다. 그런데 이게 플러그인이 없으면 추가하기가 조금 곤란합니다. 현재까지 제대로 지원하는 플러그인은 못찾았고 functions.php를 수정하는 방법이 일반적인데 그렇게하고 싶지는 않았습니다.  저렇게 하면 워드프레스를 업그레이드하거나 테마를 바꾸거나 하면 난장판이 되었던 경험이 있습니다.

그래서 간단하게 붙이는 방법은 글편집에서 워드프레스 구텐베르크 타입에 HTML 요소라는 것이 있어서 블럭 유형을 HTML으로 바꾸고 다음과 같이 HTML 코드를 넣으면 됩니다.

<script src="https://unpkg.com/mermaid@8.0.0-rc.8/dist/mermaid.min.js"></script>
<link rel="stylesheet" href="https://cdn.rawgit.com/knsv/mermaid/0.5.6/dist/mermaid.css">
<script>
mermaid.initialize({
        startOnLoad: true,
        flowchart: { curve: 'basis' } 
});
</script>

<code class="mermaid">
graph LR
  M --> e
  e --> r
  r --> m
  m --> a
  a --> i
  i --> d
</code>

소스 코드에서 js 파일의 링크과 css는 적당히 최신의 것으로 찾아서 바꾸시면 되고 Mermaid코드를 넣을 때 code 태그에 class를 mermaid로 지정하시는 부분이 핵심입니다. code 태그 대신 div나 pre 태그를 사용하면 “>” 기호와 다른 특수문자들을  인코딩(특수기호들을 &#으로 시작하는 코드로 바꾸는 것)하기 때문에 Mermaid.js가 다이어그램 문법을 해석하지 못해서 그림이 표현이 안됩니다. 현재 워드프레스에서는 code 태그의 안쪽은 HTML 인코딩을 하지 않습니다.

위의 소스코드에서 js와 css 파일을 인클루드하는 것은 따로 보관해 두거나 전에 작성했던 포스트에서 복사해서 붙이기는 방법을 쓰고  Mermaid 코드 부분만 따로 작성해서 code 태그 안쪽에 붙여 넣으면 Mermaid 다이어그램을 붙여 넣을 수 있습니다.

DiagrammeR – R 다이어그램 그리기

R 패키지중에 DiagrammeR라는 다이어그램(diagram)을 그릴 수 있게 해주는 것이 있습니다. 다이어그램은 플로우차트(flow chart), 간트 차트(gantt chart), 시퀀스 다이어그램 (sequence diagram)같은 것입니다.

RStudio의 DiagrammeR 스크린샷

다이어그램을 그릴 때 쓰는 도구는 Visio (Windows 쓰시는 분들) 아니면 OmniGraffle (Mac 쓰시는 분들) 아니면 PowerPoint 와 같이 손으로 그리는 것들이 있고 GraphViz 또는 Mermaid와 같이 정해진 문법을 텍스트로 입력하면 해석해서 시각적으로 표현해주는 도구가 있습니다.

DiagrammeR는 GraphViz와 Mermaid를 묶어서 연동해 놓은 패키지인데 3가지 방식으로 그래프를 그리게 해줍니다.

  1. Mermaid 문법을 텍스트로 입력한 후 텍스트를 해석해서 렌더링
  2. GraphViz 문법을 텍스트로 입력한 후 텍스트를 해석해서 렌더링
  3. R함수를 사용해서 노드와 엣지를 구성하고 렌더링

패키지를 사용하던 중 GraphViz는 원래 C로 만들어진 binary이므로 R에서 연동해서 사용할 수 있습니다만 Mermaid는 Javascript로 만들어져서 이 두가지를 어떻게 한꺼번에 연동해서 그래프를 시각적으로 표현하게 했는지 갑자기 궁금했습니다.
그래서 살펴봤더니 GraphViz.js와 Mermaid.js를 가져다 연동한 것이고 렌더링된 결과를 표현할 때는 htmlwidgets 패키지를 사용하는 것입니다.
그러니까 결국 웹브라우저를 열고 Javascript를 이용해서 렌더링하는 방식입니다.

그래서 R-GUI에서 렌더링을 하게 되면 웹브라우저가 열립니다.  RStudio에서 렌더링을 시도하면 연동이 되어서 웹브라우저가 실행되지 않고 그래프 패널에 바로 렌더링된 결과를 표현해 줍니다.  위에 넣어 놓은 스크린샷 이미지에서 확인할 수 있습니다.

앞서 말한 렌더링을 하는 세가지 방식중에 Mermaid 문법을 사용하는 것과 GraphViz 문법을 사용하는 것은  RStudio에 연동되어서 파일을 편집할 수 있게 제공하고 있기때문에 R 코드에서 DiamgrammeR 패키지를 로딩할 일이 없게 만들기도 합니다.

RStudio는 DiagrammeR를 연동해서 .mmd 확장자를 가지는 Mermaid 파일과 .dot 확장자를 가지는 Graphviz dot 파일을 바로 편집하고 코드 하일라이트도 되고 렌더링할 수 있도록 해줍니다.
아래의 링크에서 내용을 확인할 수 있습니다.

https://blog.rstudio.com/2015/05/01/rstudio-v0-99-preview-graphviz-and-diagrammer/

위의 3가지 방식 중 2가지는 앞서 말씀드린 것 처럼 RStudio와 연동으로 인해 DiagrammeR 패키지의 함수를 사용할 일이 없게 만들지만  DiagrammeR 함수를 이용한 방식의 렌더링을 사용하면 data.frame에 있는 데이터를 연동해서 그래프를 그릴 수 있습니다. 이게 가장 큰 장점이지요.

아래는 각각의 방법으로 만든 간단한 장난감 예제입니다. 

Mermaid 문법을 이용한 렌더링

Mermaid 문법을 이용한 예제입니다.

library(DiagrammeR)

DiagrammeR('
graph LR
subgraph ""
   N01[H] --- N02
   N02[E] --- N03
   N03[L] --- N04
   N04[L] --- N05
   N05[O] --- N06
   N06[!]
end
N06 --> N07
subgraph ""
   N07((M)) --- N08
   N08((E)) --- N09
   N09((R)) --- N10
   N10((M)) --- N11
   N11((A)) --- N12
   N12((I)) --- N13
   N13((D))
end

classDef box1 color:white,fill:#eee,stroke:#000,stroke-width:5px 
classDef box2 color:white,fill:#fff,stroke:#33d,stroke-width:5px

class N01,N02,N03,N04,N05,N06 box1
class N07,N08,N09,N10,N11,N12,N13 box2

linkStyle 0 stroke:#ddd,stroke-width:20px
linkStyle 1 stroke:#ddd,stroke-width:20px
linkStyle 2 stroke:#ddd,stroke-width:20px
linkStyle 3 stroke:#ddd,stroke-width:20px
linkStyle 4 stroke:#ddd,stroke-width:20px

linkStyle 6  stroke:#99d,stroke-width:20px
linkStyle 7  stroke:#99d,stroke-width:20px
linkStyle 8  stroke:#99d,stroke-width:20px
linkStyle 9  stroke:#99d,stroke-width:20px
linkStyle 10 stroke:#99d,stroke-width:20px
linkStyle 11 stroke:#99d,stroke-width:20px
')

GraphViz 문법을 이용한 렌더링

GraphViz 문법을 이용한 예제입니다.

grViz('
digraph boxes_and_circles {
  graph [overlap = true, fontsize = 12]
  rankdir="LR"

  subgraph cluster_2 {
    node [shape = circle,
      fixedsize = true,
      width = 0.9]
      color=none
      rank=same
      N07[label="G"]
      N08[label="R"]
      N09[label="A"]
      N10[label="P"]
      N11[label="H"]
      N12[label="V"]
      N13[label="I"]
      N14[label="Z"]
      N07->N08->N09->N10->N11->N12->N13->N14
      }
      
  
  subgraph cluster_1 {
    node [shape = box,
          fontname = Helvetica]
  	color=none
    rank=same
    N01[label="H"]
    N02[label="E"]
    N03[label="L"]
    N04[label="L"]
    N05[label="O"]
    N06[label="!"]
    N01->N02->N03->N04->N05->N06
  }
}
')

GrammerR의 R 함수를 이용한 렌더링

전통적인 스타일의 R 함수를 사용한 예제입니다. 아래의 함수들은 DiagrammeR 최신 패키지를 설치해야만 됩니다. 최근에 함수 이름에 변화가 있었던 모양입니다.

library(DiagrammeR)

nodes <-
  create_node_df(
    n = 7,
    type = "number")

edges <-
  create_edge_df(
    from = c(1, 1, 1, 1, 1, 1),
    to = c(2, 3, 4, 5, 6, 7),
    rel = "related")

graph <-
  create_graph(
    nodes_df = nodes,
    edges_df = edges)

render_graph(graph)

dplyr와 연동한 DiagrammeR

역시 RStudio에서 만은 부분을 기여한 패키지인 만큼 dplyr 스타일의 매우 스타일리쉬한 파이프라인 형태의 코딩도 지원합니다.

library(DiagrammeR)

example_graph <-
  create_graph() %>%
  add_pa_graph(
    n = 50,
    m = 1,
    set_seed = 23) %>%
  add_gnp_graph(
    n = 50,
    p = 1/100,
    set_seed = 23) %>%
  join_node_attrs(
    df = get_betweenness(.)) %>%
  join_node_attrs(
    df = get_degree_total(.)) %>%
  colorize_node_attrs(
    node_attr_from = total_degree,
    node_attr_to = fillcolor,
    palette = "Greens",
    alpha = 90) %>%
  rescale_node_attrs(
    node_attr_from = betweenness,
    to_lower_bound = 0.5,
    to_upper_bound = 1.0,
    node_attr_to = height) %>%
  select_nodes_by_id(
    nodes = get_articulation_points(.)) %>%
  set_node_attrs_ws(
    node_attr = peripheries,
    value = 2) %>%
  set_node_attrs_ws(
    node_attr = penwidth,
    value = 3) %>%
  clear_selection() %>%
  set_node_attr_to_display(
    attr = NULL)
#> `select_nodes_by_id()` INFO: created a new selection of 34 nodes
#> `clear_selection()` INFO: cleared an existing selection of 34 nodes
example_graph %>%
  render_graph(layout = "nicely")

추가로 네트워크 분석 관련 패키지인 igraph에서 생성한 객체를 DiagrammeR 형태로 변환하는 함수들도 제공합니다.  사용해 보지는 않았습니다. ^^;

앙상블 모델 – 배깅 Bagging

기계학습 부류. 분류(classification) 또는 예측(prediction)에서 여러 모델을 합쳐서 더 좋은 결과를 얻는 방법을 앙상블(Ensemble) 모델이라고 합니다.  앙상블 기법은 배깅(Bagging), 부스팅(Boosting), 스태킹(Stacking) 3종류로 나눌 수 있습니다. 
이 포스트에서는 우선 배깅(Bagging)에 대해서 설명합니다.

앙상블 모델을 배울때 보통 Bagging과 Boosting을 알게 되고 그 다음 Stacking을 생각하게 됩니다. 순서는 중요하지 않지만 기법들이 생각보다 많이  다르고 복잡함의 종류도 다릅니다. 그래서 한꺼번에 설명하기 어렵습니다.

앙상블 모델은 보통은 지도학습(supervised learning)에 사용됩니다. 군집화나 학습데이터가 없는 아웃라이어 감지(outlier detection), 어노멀리 감지(anomaly detection), 클러스터링(clustering) 같은 것에는 쓰기 어렵습니다.

비지도학습(unsupervised learning)에도 앙상블을 할 수 있다고 하는데 실제로 사례를 본적은 없습니다.

배깅 Bagging

배깅은 모델을 병렬로 연결해서 취합하는 방법입니다.
예를들어 결정트리(Decision Tree; 분류 나무)와 같은 알고리즘을 병렬로 연결한다고 하면 여러 개의 트리를 만들어서 결과를 취합합니다.  결합할 때는 다수결(Majority vote)를 쓸 수도있고 가중치(Weighted Majority Vote)를 쓸 수 있는데 기본으로는 다수결을 쓴다고 알려져 있습니다.

배깅의 대표적으로 알려진 알고리즘은 Leo Brieman이 만든 그 유명한 랜덤포레스트(Random Forest)가 있습니다. 이름이 랜덤포레스트인 이유도 배깅과 관련이 있기 때문입니다. 랜덤요소를 이용해 트리를 여러 개 만들고 합쳐서 숲을 만듭니다.

앙상블에서 모델을 몇 개를 결합할지는 보통 초매개변수(Hyper parameter)로 만드는 사람에 의해서 정해지게 됩니다. 결정트리를 앙상블로 결합하는 경우는 보통 100개 이상입니다.

배깅을 조금 구체적으로 설명하면 데이터로 입력값을 주면 Y 또는 N를 알려주는 트리모델을 결합해서 배깅으로 앙상블시키려고 하면 가지고 있는 학습데이터로 100개의 트리 모델을 만들고 실제로 판별에 사용할 때 입력을 100개의 트리모델에 주고 각 트리들이 Y과 N을 각각 던져 주면 그 중 많은 것을 답으로 취하는 방식입니다.  물론 이것은 아주 간단한 예이고 더 복잡하게도 변형이 가능합니다.

그런데 한뭉치의 학습데이터로 모델을 여러 개를 만든다고 했는데 어떻게 여러 개를 만드느냐가 의문입니다.
100개의 결정트리를 만들려면 학습데이터를 100등분해서 각각 만들면 되지 않을까라고 생각하겠지만 그렇게 나눌 양이 되지 않는 경우가 많고 학습 데이터가 부족해서 10묶음 교차검증(10 Fold Cross Validation)같은 것 까지 하는 판국에 학습데이터를 잘게 쪼개서 모델을 만들 여유가 없게 됩니다.
지도학습에서는 학습데이터의 양이 항상 문제입니다. 언제나 부족하다고 느껴집니다. 사회과학이나 의료같은 문제에서는 대량의 학습데이터를 얻기 어려운 경우가 많으니까요.  이미지 인식같은 종류의 자연과학 데이터로 부터 문제를 해결하는 딥러닝하고는 입장이 많이 다릅니다. 100등분을 해서 나눌 여유도 없고 그렇게 나누면 각각의 모형들이 편향이 생기거나 분산이 커질 여지가 많습니다.
그래서 학습데이터를 분할해서 모델을 각각 만든다는 것이 다소 비현실적인 경우가 많습니다. (다 그런것은 아닙니다)

적은 데이터로 모델을 여러개 만드는 방법은 배깅이라는 명칭을 풀어보면 알 수 있습니다.

배깅이라는 단어는 영어사전에서 찾을 수 있는 단어는 아니고 부트스트랩 어그리게이팅 Bootstrap AGGregING의 약어 입니다.

풀어서 보면 부트스트랩(Bootstrap)은 샘플을 다시 샘플링하는 것을 부트스트래핑(Bootstraping)이라고 하고 어그리게이팅은 그냥 취합한다는 뜻입니다.  즉 부트스트래핑 기법으로 학습데이터를 뻥튀기하는 효과로 여러개의 트리를 만드는데 사용하고 그 결과들을 취합합니다. 그것을 배깅이라고 부릅니다.

부트스트래핑은 통계학의 샘플링에서 매우 중요하게 다루는 개념 중 하나입니다. 어렵고 내용이 길어지므로 설명은 다음기회에 해보겠습니다.

부트스트래핑(뻥튀기)을 조금 더 쉽게 설명하면
10000개의 레코드로 된 데이터세트가 있다고 가정합니다.
10000개의 레코드를 10000번 복원추출(resampling)을 합니다. 그러면 갯수는 똑같이 10000개가 됩니다. 다시 이 과정을 반복해서 100번을 해서 10000개 짜리 데이터세트를 100개를 만들고 이 것으로 각 모델들을 만듭니다. 그러면 100개의 조금씩 다른 모델을 만들 수 있습니다.

“10000개에서 10000개를 표본추출(샘플링)하면 똑같은 것 아닌가?”
라고 생각할 수 있습니다.  또
“똑같은 것 100개를 만들어서 각각 모델을 만들면 다 똑같은 것 아닌가?”
라고 생각할 수 있습니다.
복원추출을 했기 때문에 안 똑같습니다.
복원추출은 영어로 리샘플(Resample)이라고 합니다. 가지고 있는 학습데이터가 모집단으로 부터 표본추출한 데이터라고 볼 수 있습니다. 즉 모집단에 대한 샘플데이터입니다.  
표본추출한 것을 데이터 갯수 만큼 복원추출을 다시 하게 되면 어떤 것은 같은 것이 중복해서 뽑히고 어떤것은 아예 뽑히지 않게 됩니다.  이것이 배깅의 효과인데 이게 무슨짓인가 싶겠지만 이렇게 표본을 다시 복원추출하면 원래 모집단의 특성을 더 잘 반영되도록 재구성되는 경향이 있다고 알려져 있습니다. (중심극한정리와 비슷해 보이지만 다른 것입니다)

이 특성을 이용해서 조금씩 다른 모델들을 만들고 그것들의 결과를 취합하는 것입니다. 
“데이터가 전부 비슷하니 결과도 별차이가 없겠네”
라고 생각할 수 있겠지만 데이터가 빼곡해지는 효과가 있고 조금씩 다른 모델들이 투표를 하는 방식이므로 배깅으로 만들어진 앙상블 모델은 결과들에 대한 편차가 크지 않고 안정적인 결과를 보여지도록 향상됩니다.
학습데이터가 원래 편향이 있다면 그로 인한 편향문제까지는 해결하지는 못하지만 미지의 데이터(Unseen data)에 상당히 괜찮은 성능을 보이고 노이즈나 아웃라이어에 대해서도 강해지는 것으로 알려져 있습니다.
실제로 단순한 트리모형과 랜덤포레스트 모델을 만들고 비교를 해보면 차이를 알 수 있겠습니다.

R 3.5.0 릴리즈 – Joy in playing

지난 2018-04-23에 R 3.5.0이 릴리즈 되었습니다.
이전 버전은 R 3.4.4입니다.
R 3.5.0의 닉네임은 “Joy in playing”이고 늘 그래왔듯이 이 닉네임도 만화 피너츠에 나오는 대사입니다.

https://www.gocomics.com/peanuts/1973/01/27

R 3.4.x에서 앞자리 숫자가 바뀌면서 R 3.5.0으로 올라가면서 이전의 버전업에  비해서 업데이트 내역이 조금 많습니다.

꽤 많아서 나열하기는 힘들고 그 중에서 체감할 수 있는 가장 중요한 업데이트는 R에 설치되는 패키지가 설치할 때 모두 bytecode로 컴파일 된다는 것입니다.

그래서 바로 버전업을 하면 예상치 못한 문제가 발생할 여지가 많아서 사용하던 패키지가 이상하게 작동하거나 RStudio가 오작동 한다거하는 문제가 있을 수 있습니다.

버전업을 조금 미루시거나 RStudio를 최신으로 빠르게 반복해서 업데이트 해 주는 것이 필요할 것 같습니다.

R팁 – 두 벡터의 모든 멤버가 동일한지 비교하기 all.equal

두 벡터가 동일한지 비교하는 간단한 팁입니다.

R은 벡터(vector)와 스칼라(scala)의 구분이 없이 사실은 모든 변수를 벡터로 취급하기 때문에 다른 언어에는 없는 몇 가지 문제가 생깁니다. 이것도 그것과 관련이 있습니다.

두 벡터, 즉 2개의 변수가 있고 변수가 모두 length가 2 이상일 때 두 벡터가 완전히 동일한지 비교할 때 아래의 코드에서 첫번째 if구문과 같은 실수를 합니다.

v1 <- c(1, 2, 3)
v2 <- c(1, 2, 4)

if (v1 == v2) {
  print("same")
} else {
  print("not same")
}

if (all.equal(v1, v2) == TRUE) {
  print("realy same")
} else {
  print("realy not same")
}

위의 예제 코드에서 첫번째 if 구문은 상식적으로 의도한 대로 작동하지 않습니다.   == 연산자가 두 변수의 첫번째 요소(first element)만을 비교하기 때문에 두 벡터가 같다고 나옵니다.

물론 다음과 같은 경고 메세지를 콘솔창에 뿌려주기 때문에 문제가 있다는 것을 알 수는 있습니다. 무심결에 경고메세지를 무시해 버리면 큰 문제가 생길 수 있습니다.

Warning message:
In if (v1 == v2) { :
  the condition has length > 1 and only the first element will be used

만약 두 벡터의 멤버가 모두 동일한지 비교하려면 처음 코드에서 두 번째 사용한 if 구문처럼 all.equal을 사용해야 합니다.

all.equal(v1, v2) == TRUE

코드를 조금 고쳐서 다음과 같은 것을 실행해 보세요.

v1 <- c(1,2,3)
v2 <- c(1,2,4)
v3 <- c(1,2,3)

if (v1 == v2 & v1 == v2) {
  print("same")
} else {
  print("not same")
}

if (all.equal(v1, v2) == TRUE & all.equal(v1, v3)) {
  print("realy same")
} else {
  print("realy not same")
}

all.equal(v1, v2) == TRUE
all.equal(v1, v3) == TRUE

사실 R을 사용해서 작업을 할 때 두 벡터가 완전히 동일한지 비교할 일이 별로 없습니다. 그래서 새까맣게 까먹고 있다고 가끔 실수를 저지를 때가 있습니다.