심심해서 하는 블로그 :: '분류 전체보기' 카테고리의 글 목록

하이퍼파라미터 최적화.. 중요한 건 알겠는데.. 일일히 하는게 번거롭네?


하이퍼파라미터의 최적화는 기계학습의 중요한 태스크 중에 하나입니다.

하이퍼파라미터를 어떻게 선택하는 가에 따라 Overfit 이 된 모델이 될 수 도 있고, Underfit 모델이 될 수 있습니다.

하지만 같은 종류의 모델을 사용하더라도 학습하는 데이터에 따라서 하이퍼파라미터의 값은 상이합니다.


우리는 최적화된 파라미터를 찾기 위해서 값을 조정하면서, 모델을 수행을 하고 모델을 검증을 합니다.

그리고 이걸 결과가 좋을 때까지 하이퍼 파라미터 값을 재변경해서 다시 학습 및 검증을 하는 과정을 반복합니다.

근데 이 과정이 생각보다 귀찮습니다.


Optuna : 하이퍼파라미터 최적화 프레임워크

Optuna는 하이퍼파라미터 최적화 태스크를 도와주는 프레임워크입니다.

파라미터의 범위를 지정해주거나, 파라미터가 될 수 있는 목록을 설정하면 매 Trial 마다 파라미터를 변경하면서, 최적의 파라미터를 찾습니다.


하이퍼파라미터의 범위나 목록은 아래의 방법으로 설정할 수 있습니다.


  • suggest_int : 범위 내의 정수형 값을 선택합니다.

n_estimators = trial.suggest_int('n_estimators',100,500)

  • suggest_categorical : List 내의 데이터 중 선택을 합니다.

criterion = trial.suggest_categorical('criterion' ,['gini', 'entropy'])

  • suggest_uniform : 범위 내의 균일 분포를 값으로 선택합니다.

subsample = trial.suggest_uniform('subsample' ,0.2,0.8)

  • suggest_discrete_uniform : 범위 내의 이산 균등 분포를 값으로 선택합니다.

max_features = trial.suggest_discrete_uniform('max_features', 0.05,1,0.05)

  • suggest_loguniform : 범위 내의 로그 함수 선상의 값을 선택합니다.

learning_rate = trial.sugget_loguniform('learning_rate' : 1e-6, 1e-3)

 

소스 코드

이전 포스팅에서 사용했던 XGBoost Regressor를 최적화 하는데 사용한 소스코드 입니다.

전체 소스코드는 https://www.kaggle.com/ssooni/xgboost-lgbm-optuna 를 참고하시면 됩니다.


매 Trial 마다 수행할 함수를 작성합니다.

함수 내부에는 하이퍼파라미터의 값을 정의하는 부분과 모델을 학습하는 부분을 작성하고 Loss Metric의 결과를 반환합니다.



이제 아래의 소스를 참고해서 optuna를 사용해서 최적의 파라미터를 찾아봅시다.

최적의 파라미터는 study.best_trial.params 에 저장되어 있습니다.



optuna는 추가적으로 학습하는 절차를 확인할 수 있는 시각화 툴도 제공합니다.

다양한 시각화 기법을 제공하지만 저는 매 Trial 마다 Loss가 어떻게 감소되었는 확인 할 수 있는 함수와

하이퍼 파라미터 별로 중요도를 확인할 수 있는 함수를 소개 합니다.



도움이 되셨으면, 아래의 공감 버튼을 눌러주세요.

공감 버튼은 작성자에게 큰 동기부여가 됩니다.

,

캐글에서 사랑받는 XGBoost

요즘 캐글에서 문제를 도전해보는 재미로 공부를 하고 있습니다.

최근에는 Tabular Playground Series - Jan 2021[ kaggle.com/c/tabular-playground-series-jan-2021 ] 를 푸는 중에 다른 사람들이 올려놓은 노트북을 참고 해보니,

대부분이 XGBoost를 사용해서 문제를 풀고 있는 것을 확인 했습니다.

왜 이렇게 사랑 받고 있는지 궁금해서 직접 사용해보고 느낀 점을 정리해서 포스팅 해보곘습니다.


앙상블 기법 : Boosting

그 전에 XGBoost에서 사용하는 Boosting이라는 앙상블 기법에 대해서 간단하게 설명을 해보겠습니다.

앙상블은 직관적으로 "집단 지성" 이라고 생각을 하면 편리합니다. 여러 개의 모델을 사용해서 각각의 예측 결과를 만들고 그 예측 결과를 기반으로 최종 예측결과를 정하는 방법이죠


그 중 Boosting이란 방법은 비교적 약한 모델들을 결합하여 하나의 강한 모델 만드는 과정입니다.

모델 A이 예측한 결과를 다음 모델인 B에서 보완하여 예측하고 최종적으로 C에서 보완을 해서 예측 결과를 만들어 내는 방식이죠.

Boosting을 사용하는 대표적인 모델은 AdaBoost, Gradient Boost 등등이 이 있고 XGBoost는 그 중 Gradient Boosting 을 사용해서 모델링을 합니다.


Gradient Boosting 설명을 수학없이 간결하게 해준 유튜브 영상이 있어서 링크를 남겨 둘게요.

이거보다 설명 잘 할 수 없을거 같아요. 꼭 보세요. 두 번 보세요.

https://www.youtube.com/watch?v=3CC4N4z3GJc


XGBoost : Extreme Gradient Boosting

장점 : 계산 속도 향상, Kaggle에서 수차례 우승한 경력

sklearn 에도 Gradient Boosting 로 Regression 또는 Classification 을 할 수 있는 매소드를 제공하고 있습니다만, 시간이 너무 오래 걸립니다.

하지만 XGBoost의 경우에는 병렬 연산을 지원하여 계산 속도를 향상 시킵니다.

또한 GPU를 사용 할 수 있는 옵션을 제공하기 때문에 학습하는데 소요되는 시간을 절약할 수 있습니다.

이것은 하이퍼파라미터 튜닝을 할 때도 특히 빛이 납니다. (CPU 만으로 50회를 돌리다 하루가 넘게 소요되서 당황스러웠던 기억이 있습니다.)

 

XGBoost는 Overfitting 을 Overfitting 을 제어 하기 위해 두 가지 방법을 제안합니다.

1. 모델의 복잡도를 제어하는 방법 : max_depth, min_child_weight, gamma 하이퍼파라미터 사용

2. 다음 모델로 Loss 를 전달할 때 Random Value를 추가하는 방법 : subsample, colsample_bytree 파라미터 사용

 

그 외에도 early_stopping_rounds 를 사용해서 눈에 띄지 않게 Loss가 감소하지 않은 경우에는 학습을 진행하지 않도록 제어도 가능합니다.

학습 성능의 경우에는 kaggle의 수차례 우승한 사례를 봤을 때, 충분히 우수하다고 판단됩니다.


 

 

단점 : Tree Based Learning, 복잡한 하이퍼 파라미터

 

모든 학습 알고리즘이 그렇듯, 장점과 단점이 있기 때문에 데이터의 분포나 상황에 알맞게 사용해야 합니다.

 

1. Tree Based Learning 를 학습할 때 학습 데이터에 예민하게 반응합니다.

학습 데이터에서 1~10 사이의 범주를 가진 라벨로 학습을 진행한 모델이 있다고 합시다.

이 모델은 데이터의 예측을 1 ~ 10 사이의 값으로 예측을 하고자 하고 10 초과, 1 미만으로 반환을 하기 어렵습니다.

 

2. 적절하게 튜닝을 하지 않은 모델은 Overfit이 쉽게 발생하는 문제 때문에 반드시 진행해야하는 절차입니다.

그런데 XGBoost는 튜닝을 할 때 손봐야할 파라미터가 너무 많습니다.

(당장 xgboost 공식 홈페이지에 가서 내용을 봐도, 파라미터가의 수가 너무 많습니다.)


소스코드

https://www.kaggle.com/ssooni/xgboost-lgbm-optuna

소스코드에는 XGBoost 외에도 LightGBM, Optuna, Stacking 앙상블을 사용한 내용도 있습니다.


,

이전 포스팅 : 2019/01/15 - [Data Mining] - [Python] 공공데이터 API 사용기 (feat 미세먼지)

GitHub : https://github.com/ssooni/data_mining_practice

소소한 근황 소개

미세먼지 API를 사용하는 것을 이후로 SVM이나 Correlation 적용하는 등의 시도를 하였으나,

생각보다 결과도 좋지 않았고 내가 알고 있는 지식의 한계가 와서... 대학원을 가버렸다(응?)

또 약간의 지식을 배웠으니까 블로그에 정리를 해봅니다.


Sequence Model

앞선 포스팅에서 매 시간마다 측정한 미세먼지 및 기상 데이터를 수집을 하였습니다.

미세먼지나 기상데이터의 특징이라고 한다면 시간에 따른 순서가 있는 데이터라는 점입니다.

이러한 종류의 데이터를 모델링하는 걸 Sequence Model이라고 하며 RNN, LSTM, 이번에 다룰 GRU 등이 있다.

Sequence Model에 주로 적용하는 데이터는 주식이나 텍스트 등이 있다.

(우리가 사용하는 언어도 어순이 있기 때문에 Sequence Model 대상이다.)


일반적으로 사용하는 신경망에서 Sequence Data 를 사용하기 어려운 이유는 2가지입니다.

입력 데이터와 출력 데이터의 길이가 가변적이다.

대표적으로 CNN은 입력값이나 출력값의 길이가 모델을 학습하거나 적용할 때 동일합니다.

하지만 우리가 주로 보는 동영상이나 책을 입력 데이터로 한다면 각각의 데이터의 길이가 다른데,

이러한 점은 일반적인 인공 신경망 알고리즘에는 적합하지 않은 데이터입니다.

(일부 논문에서는 0으로 부족한 공간을 채워서 연구한 사례가 있긴 합니다.)

 

일반적인 신경망 알고리즘에서는 데이터의 순서를 반영하지 않습니다.

데이터를 섞어도 학습 결과가 크가 변화하지 않습니다.

즉, 데이터의 선후 관계가 전혀 반영이 되지 않는다는 점이 한계입니다.


RNN의 구조

뒤에서 자꾸 무언가를 준다..

RNN의 학습하는 과정을 도식을 하면 아래와 같이 보통 표현합니다.

 

RNN 구조 도식

파란 색으로 부분을 cell 이라고 합니다.

각각의 cell은 매 단계 마다 일렬로 입력되는 input x와 이전 단계의 결과로 산출된 hidden 값 h를 입력으로 받고 출력으로 예측 값인 y 와 다음 단계로 전달할 hidden 값을 전달합니다.


매 단계마다 이전 단계에서 만들어진 hidden 값을 사용하여 y 값을 생성하기 때문에
입력 값의 순서가 다르면 hidden 값 역시 다르기 때문에 앞서 말한 선후 관계가 반영되지 않은 문제점을 해결할 수 있습니다.

또한 RNN은 다양한 Input 과 Output 의 구조를 가질 수 있습니다.
1:N, N:M, N:1 등의 다양한 구조의 데이터를 적용합니다.


GRU : Gated Recurrent Unit

RNN은 단점이 있습니다. Cell 내부에서는 실제로 소수점 단위의 계산이 이뤄지고 있고 그 결과로 h 값이 발생하는데,

Sequence가 너무 긴 데이터의 경우에 0에 가까운 값이 오고 가게 되어 컴퓨터가 계산할 수 없는 작은 수로 수렴하게 되는 문제가 있습니다.

Gradient Vanishing 으로 불리는 이 문제는 이전 단계의 정보를 선택적으로 학습을 하는 즉, "줄 건 줘" 방식으로 계산하는 LSTM이나 GRU를 통해 개선이 되었습니다.


그리고 이전 단계의 데이터의 반영과 현재 데이터에 대한 반영의 비율을 Gate 를 통해 제어를 합니다.
GRU는 Reset / Update 두 종류의 게이트를 사용해서 학습하는 모델입니다.


Reset Gate

Reset Gate 에서는 이전 단계의 Hidden status 값과 현 단계의 x 값을 Sigmoid 함수에 적용하여 0 ~ 1 사이의 값을 얻습니다.

 이 값을 이용해서 과거(이전 데이터)의 Hidden 정보를 현재의 정보를 구하는데 얼마만큼 반영 할 것인지 정합니다.


Update Gate

Update Gate 에서도 이전 단계의 Hidden status 값과 현 단계의 x 값을 Sigmoid 함수에 적용하여 0 ~ 1 사이의 값을 얻습니다.

물론 Reset Gate의 Weight 와 다른 Weight를 이용해 계산합니다. 

이 값을 이용해서 전체 데이터의 양을 1로 했을 때 현재의 정보를 반영할 비율을 $z_t$, 과거의 정보를 반영할 비율을 $1-z_t$ 로 합니다.
이제 현재의 정보를 아래의 식을 이용해서 구합니다. 

Reset Gate에서 구한 $r_t$를 이용해서 현재 정보를 얻습니다.

마지막으로 현재의 HIdden Status를 과거의 Hiddent Status와 현재의 정보를 앞서 구한 비율 $z_t$ 를 적용하여 계산 후 다음 단계로 전달합니다.

 

 

Pytorch 로 적용 해보기

전체 소스 코드 : https://github.com/ssooni/data_mining_practice/blob/master/dust_weather/GRU.ipynb

class GRU(nn.Module):
    def __init__(self, n_layers, hidden_dim, input_dim, n_classes=1, dropout_p=0.2):
        super(GRU, self).__init__()
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim

        self.dropout = nn.Dropout(dropout_p)
        self.gru = nn.GRU(input_dim, self.hidden_dim, num_layers=self.n_layers, batch_first=True, dropout=dropout_p)
        self.fc = nn.Linear(self.hidden_dim, n_classes)

    def forward(self, x):
        h0 = self._init_state(x.size(0))
        out, (hn) = self.gru(x, (h0.detach()))
        out = self.fc(out[:, -1, :]) 
        return out

    def _init_state(self, batch_size=1):
        weight = next(self.parameters()).data
        return weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(DEVICE)

 

전체 소스코드와 실행 결과를 GitHub 에 올려 두었습니다. 
매 시간 측정된 날씨 데이터와 미세 먼지 데이터를 이용해서 예측을 해보는 것을 해보았습니다.
실습용으로 사용할려고, 전처리도 간단하게 하고 프로그램도 간단하게 구현했습니다. 
참고용으로만 사용하시고, 원하는 도메인에 적절하게 적용하세요

 

> 공감 버튼은 작성자에게 큰 동기부여가 됩니다.

,

마라탕 첫 느낌

요새 감자님이 "맛있는 녀석들" 을 왓챠플레이를 보면서 정주행 중인데, 그거 덕분에 데이트 메뉴가 다채로워 지는거 같았습니다.

감자님은 원래 매운 걸 먹는 걸 좋아해서 서울에 맵다는 곳은 다 돌아다니면서 먹었어요.

물론 항상 약국에서 위장약 사와서 힘들어 하기도 했었구요





그래서 마라탕을 먹는다고 했을 때 겁도 많이 났었지만, 매운 걸 잘 못 먹는 지인이 중국 여행을 다녀오고 나서 맛있다고 자랑을 해서

그래, 한 번 먹어보자는 생각으로 갔던거 같아요.

마라탕을 먹으러 갔는데 주변에 생선을 엄청나게 굽고 있더라고요.

생선구이 골목이라고 하는데 연기가 자욱했지만 그 곳도 궁금하긴 하네요


매운 단계는 0단계부터 아주 맵다는 4단계 총 5단계로 되어있어요

저희 둘은 양고기 3단계와 소고기 3단계 마라탕을 주문하고, 면은 옥수수면이 좋다해서 옥수수면으로 했어요.

사이드 메뉴로 쇼좌빙이라는 걸 주문했어요.





고소한 땅콩향과 알싸한 맛

3단계를 시켜서 맵지 않을까 걱정했는데, 신라면 국물 정도로 매웠어요.

국물은 땅콩 그 자체였기 때문에 땅콩 알르레기가 있는 사람이라면 미리 말하는게 좋습니다.




양고기나 소고기나 국물맛이 크게 다르지는 않았어요. 양고기를 더 좋아하는 취향이면 양고기를 드셔도 될거같아요

맨날 탱글탱글한 면을 먹다가 축쳐진 옥수수면을 먹으니까 익숙하지 않은 식감이였어요.

다음 번에는 다른 면을 먹어 봐야겠어요. 개인적으로 옥수수 면은 식감때문에 썩 좋지는 않았습니다.




감자님이 고수를 먹을 줄 알아서 고수를 넣어서 먹었는데 그냥 먹는 거보다 맛있다고 꼭 넣어 먹어라는데

저는 고수를 싫어하기 때문에, 전혀 공감가지 않았습니다 ㅋㅋㅋ

고수를 좋아하는 분들은 한 번 넣어서 드셔보세요. 고수는 셀프로 먹고 싶은 만큼 퍼서 드실 수 있어요.




쇼좌빙이 왔는데 크로와상을 넓게 펼친 듯한 비주얼이였어요. 연유와 함께 주는데 별미였어요.

야채 크래커 향과 연유에 달달한게 만나서 맛이 좋더라구요.

뭔지 궁금해서 호기심에 지른 건데 조금 기름지긴 했지만 맛이 좋아서 기분이 좋았습니다.



먹을 땐 괜찮았는데..

먹다 보니까 점점 매워지긴 했지만 이 정도는 견딜만하다 생각했었는데, 위장은 그렇지 않았나봐요 ㅋㅋㅋㅋㅋ

저녁에 감자님이랑 저랑 각자 화장실에서 고생을 좀 많이 했습니다.

매운 걸 먹을 때마다 다음 날에 심하게 고생하는 타입이라면 괜한 용기 내지 마시고, 단계를 낮춰서 드시는게 좋을거 같네요.




긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 힘이 됩니다. 감사합니다.


,

위치 : 한가람 미술관

전시일 : 2018.12.06(목) ~ 2019.03.10(일)

관람시간(~2월) : 오전 11시 - 오후 7시 (입장마감 오후 6시)

관람시간(3월) : 오전 11시 - 오후 8시 (입장마감 오후 7시)


이번 주말에는 존 레논 이매진 전시회를 다녀왔습니다.

예술의 전당에서 매번 커피나 마셨지 전시회를 직접 찾아 간적이 없어서, 아쉬웠는데 위메프에서 할인하는 것을 보고 후다닥 예매를 했습니다.

존 레논이 음악사의 한 시대를 풍미했던 인물이였기 때문에 사람들이 많이 방문했습니다.


저는 어릴 적 "20세기를 빛낸 사람들" 이라는 만화책에서 비틀즈 편으로 존 레논의 존재를 처음으로 알게 되었습니다.

그 때 느낀 존 레논의 이미지는 "제멋대로, 독특한 사상을 가진" 사람으로 느껴졌습니다.

다른 여자에 빠져 가정에 충실하지 않고 그룹 맴버들과 서먹해하는 그가 이기적이라고 생각도 했었습니다.

20대가 된 지금은 어떻게 받아드려질까 생각하면서 전시회를 관람하였습니다.


감자님은 걱정이 많았습니다.

비틀즈의 몇몇 노래는 무한도전을 통해서 들었지만, 맴버가 누가 있고 그들에 대한 정보를 전혀 몰랐기 때문입니다.

그래서 도슨트를 이용할까 하고 이용시간을 확인하였습니다.


도슨트 제공 시간

월 화 : 11:30 / 14:00 / 17:00

수 목 금 : 11:00 / 13:00 / 15:00 / 17:00

(주말에 더 이상 도슨트 서비스를 제공하지 않는다고 한다.)


입구 쪽에서 천천히 관람을 하면서 도슨트 분을 기다렸는데, 피리부는 사나이 마냥 우루루 모이는 것을 보고 포기했습니다.

물론 음성으로 안내하는 앱이나 음성안내 장치를 전시회에서 제공하니, 이 기회에 존 레논에 대해서 알아 보겠다고 생각하시는 분이라면 좋은 선택지가 되겠습니다.


존 레논의 사망으로 시작

전시회의 시작은 존 레논이 광팬이였던 마크 채프먼의 총을 맞고 사망한 사건에서부터 시작합니다.

뉴스 속보와 당시 슬퍼하는 수많은 사람들의 비통한 표정들이 존 레논이라는 인물이 얼마나 사랑을 받았는지 알 수 있었습니다.

(참고로 마크 채프먼은 종신형을 받고 아직 수감생활 중입니다.)


존 레논과 비틀즈의 탄생 그리고 성공


다음 관에서는 존 레논의 어릴 적 가정사와 비틀즈의 탄생에 대해 얘기합니다.

어느 시대이든 고난을 극복하는 사람이 시대를 대표하는 위인이 될 수 있나 봅니다.

어린 시절 존 레논은 하나뿐인 어머니를 교통사고로 잃는 등 힘든 시절을 보냅니다.


폴 매카트니와 조지 해리슨과 함께 밴드 생활을 하던 그들의 재능을 브라이언 엡스타인이 끈질기게 구애를 합니다.

설득이 끝에 결국 그가 매니저로 임명되었으며, 마지막에 합류한 드러머 링고 스타까지 합류하면서 우리가 아는 비틀즈가 활동을 시작합니다.

비틀즈는 그 후 내는 앨범마다 유럽과 미국에서 크게 성공을 하는 얘기들이 있습니다.




오노 요코와 만남


그의 인생은 오노 요코를 만남으로 크게 변화합니다.

존 레논과 오노 요코는 예술적인 영감 교류로 그들의 사상을 확장하지만, 비틀즈 맴버들과 거리가 멀어지면서 비틀즈는 해체합니다.

그 후 사랑과 평화라는 사상을 존 레논의 영향력으로 다양한 운동을 통해 세상을 향해 표출합니다.

"BED-IN", "bagism" 등 존 레논이 했던 행위 예술들을 전시회에서 체험해 볼 수 있습니다.



아빠 존 레논



존 레논이 오노 요코 사이에서 태어난 아이를 위해 5년간 육아를 하면서 그린 그림들과 사진들이 전시되어 있습니다.

그림들이 간결한 선들로 되어 있으나 인물의 특징을 잘 표현되어 있습니다.

작곡 능력에 그림과 시까지 지금까지 살아 있다면 더 많은 작품들을 남겼을텐데 하는 아쉬움이 들었습니다.

그리고 존 레논의 사진들이 전시되어 있는데, 남자가 봐도 잘 생겼습니다.



잘생겼다..


마치며 : Imagine


전시회의 마지막에는 피아노 한 대와 Imagine 뮤직 비디오가 틀어진 스크린이 있습니다.

Imagine 노래를 들으면서 저는 존 레논이 "몽상가(Dreamer)" 라고 생각이 들었습니다.

하지만 몽상가가 결코 나쁘다고 생각은 하지 않습니다.


비행기가 "내가 하늘 날 수 있으면 좋겠다" 꿈에서 시작되어 발명이 되었듯이,

그가 꿈꾸던 사랑과 평화가 가득한 세상을 우리가 함께 꿈꾼다면 인류는 언제나 그렇듯 꿈을 이루리라 생각합니다.

각박한 세상 살이에 서로를 탓하기 바쁜 우리 세상도 사랑으로 가득하길 기원합니다.


참고사항

  • 하나하나 꼼꼼하게 읽으면서 작품을 관람하니까 1시간정도 걸렸습니다.
  • 사진을 찍는 것은 자유로우나 동영상을 촬영하는 것은 금지되어 있습니다.
  • 매 챕터마다 비틀즈 또는 존 레논의 음악을 틀어 줍니다.


사진들







그래서 나는 오늘도 즐겁게 시간을 낭비했습니다 ^~^


어느 전시회처럼 기념품도 제공합니다.

감자님한테 등짝 맞을까봐 참았습니다.


비틀즈하면 떠오는 장면 중 하나. 

여기서 사진을 찍는 사람들이 정말 많습니다.


긴 글 짧은 글로 뽑을 수 있는데 존레논 외의 다른 사람이 한 명언들도 있다.



전쟁 끝!! 너가 원한다면!!



필름 속의 존 레논과 오노 요코

필름 사진기로 사진을 찍어야 누릴 수 있는 감성



Hey Jude를 부르는 폴 매카트니




긴 글 읽어 주셔서 감사합니다.

공감버튼은 작성자에게 큰 동기 부여가 됩니다.

,

GitHub : https://github.com/ssooni/data_mining_practice


새로운 시리즈 시작!!

이전 카카오 시리즈에 이어서 미세먼지 데이터와 기상 데이터를 이용해서 데이터 마이닝 기법들을 적용하는 연습을 해보았습니다.

(그거 보다 궁금했어요.. 기상과 미세먼지와 밀접한 관계가 있는지 말이죠)

데이터 수집, 전처리, 상관 분석 등 적용 해보고 싶은 방법들을 적용하고 결과를 관찰하도록 하겠습니다.


미세먼지 데이터 수집



서울시 열린데이터광장(http://data.seoul.go.kr/)에서 시간별 평균 대기오염도 정보 데이터를 제공합니다.

서울의 구 단위로 시간단위 미세먼지 농도를 제공하고 2009년 데이터도 존재하길래 사용하기 적절하다고 판단하여 인증 키를 취득하였습니다.

회원가입 절차를 진행한 후에 간단하게 사용 목적을 명시하면 인증 키를 획득할 수 있었습니다.

데이터의 저작권은 출처를 밝히면 자유롭게 사용이 가능합니다.




기상 데이터 수집


열린데이터광장에 서울시의 과거 기상데이터도 제공하지만, 데이터가 썩 만족스럽지가 않아서 기상자료개방포탈(https://data.kma.go.kr/cmmn/main.do)에서 기상데이터를 수집하였습니다.

기상포탈에는 Open-API 서비스로 다양한 종류의 기상 데이터를 제공합니다.

저는 시간 단위로 측정된 미세먼지 데이터를 수집하였기 때문에 기상 데이터도 종관기상관측 시간자료 를 이용하였습니다.

마찬가지로 회원가입을 진행한 후에 API 사용 신청을 하면 별다른 승인 절차 없이 API 사용이 가능합니다.

API Key는 마이페이지 > 오픈 API 현황에서 확인 가능합니다.




API 호출 프로그램 작성

전체 소스코드는 GitHub를 참조바랍니다.

API Key 및 전체 데이터의 경우 공유가 힘든 점 양해바랍니다.


REST API는 Http URL의 요청을 처리하여 우리가 보통 사용하는 WEB 서비스는 Response를 HTML로 하는 것과 달리 JSON이나 XML 등의 형태로 Response하는 API 입니다.

Python에는 requests 모듈이 있어서 http url만 입력하면 reponse를 획득할 수 있습니다.


미세먼지 데이터 API

먼저 서울시 시간별 평균 대기오염도 정보 데이터 API를 호출하는 프로그램을 작성하였습니다.

URL에 특정일자를 넣어주면 사용자가 설정한 start_index에서 end_index까지 데이터를 불러오는 API 구조입니다.

list_total_count 라고 해당 호출의 최대 index 값을 제공하므로 아래의 절차로 프로그램을 구상하였습니다.


  1. 최초 호출에서 list_total_count를 획득한다.
  2. 다음 호출에서 end_index 값을 list_total_count까지 사용한다.
  3. 날짜별로 데이터를 CSV로 저장한다.

## callAPI.py
def call_api(api_name, start_date, end_date, dir_name):
    # API 키는 공개하기 힘든 점 양해 바랍니다.
    api_key = open("./raw_data/api_key").readlines()[0].strip()
    url_format = 'http://openAPI.seoul.go.kr:8088/{api_key}/json/{api_name}/1/{end_index}/{date}'
    headers = {'content-type': 'application/json;charset=utf-8'}

    for date in pd.date_range(start_date, end_date).strftime("%Y%m%d"):
        # 최초 1회 Call은 해당 일자의 데이터 수를 확인한다.
        url = url_format.format(api_name=api_name, api_key=api_key, end_index=1, date=date)
        response = requests.get(url, headers=headers)
        end_index = response.json()[api_name]["list_total_count"]
        print("Max Count(%s): %s" % (date, end_index))

        # 해당 일자의 모든 데이터를 불러온다.
        url = url_format.format(api_name=api_name, api_key=api_key, end_index=end_index, date=date)
        response = requests.get(url, headers=headers)
        result = pd.DataFrame(response.json()[api_name]["row"])

        # 수집된 데이터를 CSV로 저장합니다.
        result.to_csv("./raw_data/%s/dust_%s.csv" % (dir_name, date), index=False, encoding="utf-8")

        # API 부하 관리를 위해 0.5초 정도 쉬어 줍시다 (찡긋)
        sleep(0.5)





기상 데이터 API


다음은 기상데이터 API를 사용하는 함수를 작성하였습니다.

이 API는 조회 시작 일자와 시간 / 최종 일자와 시간, 가져올 데이터의 수를 Param으로 사용합니다.

안전하게 저는 1회 요청시 하루 단위로 넉넉하게 최대 100건 정도를 요청하였습니다.

매 시간 단위로 측정되는 데이터라 많아야 24건이기 때문이죠.

결과는 마찬가지로 CSV 파일로 저장하였습니다.


## callAPI.py
def call_weather_api(start_date, end_date):
    # API 키는 공개하기 힘든 점 양해 바랍니다.
    api_key = open("./raw_data/weather_api").readlines()[0].strip()
    url_format = 'https://data.kma.go.kr/apiData/getData?type=json&dataCd=ASOS&dateCd=HR&startDt={date}&startHh=00&endDt={date}&endHh=23&stnIds={snt_id}&schListCnt=100&pageIndex=1&apiKey={api_key}'

    headers = {'content-type': 'application/json;charset=utf-8'}
    for date in pd.date_range(start_date, end_date).strftime("%Y%m%d"):
        print("%s Weather" % date)
        url = url_format.format(api_key=api_key, date=date, snt_id="108")
        response = requests.get(url, headers=headers, verify=False)
        result = pd.DataFrame(response.json()[-1]["info"])
        print(result.head())
        result.to_csv("./raw_data/weather/weather_%s.csv" % date, index=False, encoding="utf-8")

        # API 부하 관리를 위해 0.5초 정도 쉬어 줍시다 (찡긋)
        sleep(0.5)



데이터 종합

미세먼지와 기상 데이터를 2009년 1월 1일부터 2019년 1월 1일 구간으로 수집하였습니다.

두 개의 데이터는 공통적으로 날짜 컬럼이 있기 때문에 날짜 컬럼을 기준으로 Join을 해주었습니다.

데이터의 양이 많아진 만큼 File I/O 속도가 느린 CSV 포멧이 아닌 HDF 포멧으로 변경하였습니다.


## callAPI.py
def concat_data():
    df_list = list()

    # ./raw_data/dust 아래의 모든 파일을 읽습니다.
    for root, dirs, files in os.walk("./raw_data/dust", topdown=False):
        for name in files:
            df_list.append(pd.read_csv(os.path.join(root, name)))

    dust = pd.DataFrame(pd.concat(df_list, sort=False))

    # Datetime 형태로 Index를 변경해줍니다.
    dust["MSRDT"] = dust["MSRDT"].apply(lambda x: dt.datetime.strptime(str(x), "%Y%m%d%H%M"))
    dust = dust.set_index("MSRDT")

    df_list.clear()

    # ./raw_data/weather 아래의 모든 파일을 읽습니다.
    for root, dirs, files in os.walk("./raw_data/weather", topdown=False):
        for name in files:
            df_list.append(pd.read_csv(os.path.join(root, name)))
    weather = pd.DataFrame(pd.concat(df_list, sort=False))

    # Datetime 형태로 Index를 변경해줍니다.
    weather["TM"] = weather["TM"].apply(lambda x: dt.datetime.strptime(x, "%Y-%m-%d %H:%M"))
    weather = weather.set_index("TM")

    # join() 함수는 같은 index 끼리의 join을 제공합니다.
    weather.join(dust, how="inner").to_hdf("./raw_data/data.hdf", "master")
    dust.to_hdf("./raw_data/data.hdf", "dust")
    weather.to_hdf("./raw_data/data.hdf", "weather")


제공받은 데이터에서 미세먼지에 해당하는 컬럼은 PM10 또는 PM25가 있습니다.

각각 10μm, 2.5μm 이하의 먼지의 농도라고 하는데 그 중에서 초미세먼지 농도에 해당하는 PM25를 사용하고자 한다.

데이터의 분포를 확인하고자 Box Plot을 그려서 확인하고자 한다.

이 때 지역별로 확인해 보는 것이 눈에 더 잘 보일거 같아서 확인해 보았는데..




아니 저 수치가 정말 나올 수 있는 수치인가요..??

서대문구는 무슨 일이 있었던 걸까요..??

노이즈라고 생각이 드는데 다음 포스팅에서 어느 정도 전처리를 하고 사용해야 할 거 같아요.

2021.01.06 : 기상청에서 관리하던 데이터를 공공데이터포털에서 관리하는 걸로 변경이 되었습니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다.


,
Tayasui Sketches 리뷰

Tayasui Sketches 리뷰

2019년 1월 기준 아이패드 프로 2세대와 애플 펜슬 1세대 사용 유저입니다.

그림을 발로 그리고 있는 초보자의 입장에서 작성된 리뷰입니다.



애플 펜슬도 샀겠다.

누구나 그렇듯 애플 펜슬을 샀으니 이제 내 마음 속에서 없던 화가 세포를 깨웁니다.

참고로 중학교, 고등학교 미술시간마다 C,D를 차지할 만큼 그림에 자신이 없지만, 자그마치 10만원 넘는 연필을 샀으니 (ㅠㅠ) 그림을 안 그려 볼 수는 없었습니다.


그림을 전문적으로 그리는 것이 아니라 무료 앱이 가장 큰 선정 기준이였습니다.

procreate가 좋은 건 금손 분들의 작업 영상 만으로도 충분히 느꼈지만, 발로 그리기 때문에 만 원이라는 거금을 선뜻 투자하긴 힘들었습니다.


다양한 무료 그림 앱이 있지만 Tayasui Sketches는 생산성 카테고리의 무료 앱에서 높은 순위를 오랫동안 차지하고 있는 앱이라 일단 설치해보고 그림을 발로 그려 보았습니다.

약 한 달동안 사용하면서 느낀 장단점을 소개해드리겠습니다.


장점 1 : 필요한 것만 딱 있다.

무료버전에서 제공하는 툴들


설정을 다양하게 할 수 있는 것은 전문적으로 사용하는 사람에게는 큰 이점이라 생각합니다만, 초심자들에게는 심플한 것이 최고입니다.

무료 버전에서 많은 도구들을 제공하지 않지만, 기본 설정 만으로도 초보자 기준입니다만 그림을 그리는데 딱히 불편함을 느끼지 않았습니다.

색상도 자주 사용하는 색상들이 미리 배치되어 있기 때문에 그 색상에서 약간씩 수정하면 다른 색상으로 만들 수 있다는 게 장점이였습니다.

(그라데이션 기능이 무료 버전에 없는 것은 조금 아쉽지만...)


장점 2 : 뒤로 가기 제스쳐 기능

Tayasui Sketches는 그림을 그리다가 두 손가락으로 톡 쳐서 취소가 가능합니다.

이게 뭐 대단한 기능이냐 하겠지만, 그림을 그리다가 실행 하기 전으로 되돌리기를 하는 경우에 다른 손으로 뒤로가기 버튼을 찾아서 누르는 것이 생각보다 정말 귀찮고, 그림을 그리다가 맥이 끊기고 합니다.

하지만 이 앱에서는 그림을 그리다가 두 손가락으로 톡 화면을 두드리면 취소되니까 편리했습니다.

이게 익숙해지면 다른 앱에서도 실행 취소를 하기 위해 두 손가락으로 톡 화면을 두드리는 것을 경험을 하게 될 겁니다.


장점 3 : 동영상 촬영 기능

요새는 유튜브에서 금손 분들이 그림 그려지는 과정을 보여주는 영상을 올리시는 분들이 있는데, 이 앱에서는 그런 영상을 만들기 위한 동영상 쵤영 기능이 있습니다.

물론 타입랩스 같은 것은 다른 편집 앱을 통해서 사용해야 합니다.



단점 1 : 부족한 레이어

레이어를 생성할려고 하면 프로버전 구입 팝업이 뜬다


무료 버전에서는 레이어가 단 2개만 제공됩니다.

레이어 생성 기능이 프로 버전에서 제공되는 기능 이라, 제공해준 2개의 레이어 중 하나를 삭제한 후 새로운 레이어를 만들기를 시도하면, 프로로 업그레이드가 필요하다는 팝업을 접할 수 있습니다.

물론 저같이 초보자는 2개로도 감사히 잘 쓰고 있지만, 가끔 레이어 하나만 더 있었으면 좋겠다고 생각이 들곤 합니다.


단점 2 : 팜 리젝션

다른 도구들을 사용할 때 팜 리젝션 부분에서는 불편함이 없었습니다.

애플 펜슬을 사용하다가 손가락으로 터치를 시도하면 애플 펜슬 사용 중이니까 손가락으로 그림은 안 그려진다고 끊임 없이 메세지를 보여 주니깐요.


그만 채워져라 좀!!! 빡치게 하지말고!!


하지만 영역 채우기 툴에서 팜 리젝션이 간혹 안되는 경우가 있습니다.

손가락으로 툭 쳐서 색을 칠하는 기능과 선을 그려서 영역을 만들어주면 해당 공간을 채워주는 기능이 한 도구안에 공존하다 보니, 펜슬을 잡은 손이 화면에 닿으면 색이 채워집니다.

손가락으로 툭툭쳐서 색을 칠하시는 분들은 상관 없겠지만, 저는 영역을 만들어 준 뒤 채우는 것을 선호하다 보니까 이 부분이 상당히 불편하고 거슬렸습니다.



총평 : 별 4개


그라데이션이나 레이어 추가와 같이 있으면 더 편하겠다 하는 기능들은 프로 버전을 통해서 구매가 가능합니다만, 그림을 가볍게 그리는 목적으로 사용하기에는 충분한 가치를 합니다.

아주 가끔 프로 업그레이드를 권장하는 팝업이 뜨긴 하지만 시도 때도없이 15초 짜리 동영상을 보여주는 앱들에 비하면 상당히 매너있는 편이라 생각합니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다. 

,

[Python] List 중복제거

Tip 2019. 1. 4. 00:38
리스트 중복 제거

리스트 중복 제거

필요에 따라서 List 등의 중복 제거를 해야하는 경우가 있는데요.

다양한 데이터 타입에서 중복을 제거하는 방법에 대하여 알아보도록 합시다.




일반적인 경우

Python에는 set자료형을 기본적으로 제공합니다.

set은 수학에서 사용하는 집합의 개념으로 중복된 원소를 가지지 않는 것이 특징입니다.


set타입은 list 타입과 Type Cast가 가능한 타입인데 이 점을 이용하여 중복을 제거 하는 것이 가능합니다.


dup_list = ['1', '1', '1', '4', '5', 5]
remove_dup = list(set(dup_list))
print(remove_dup)



실행 결과를 보면 중복이 제거되어 있음을 알 수 있습니다.

다만 '5'5 처럼 타입이 서로 다른 경우에는 중복을 제거를 하지 않습니다.

만약 리스트 안에 리스트가 있는 경우에 이 방법을 사용하면 어떤 결과가 발생할까요?





원소가 list로 이루어져 있는 경우

dup_list = [[1,2], [1,2], [1]]
remove_dup = list(set(dup_list))
print(remove_dup)


이 소스 코드를 실행하면 에러가 발생합니다. Hash를 지원하지 않는 list를 사용하였기 때문입니다.  

다행히 tuple의 경우에는 hash를 지원합니다

list를 모두 tuple타입으로 변환한 후 중복 제거를 시도합니다.

dup_list = [[1,2], [1,2], [1]]
remove_dup = list(set(map(tuple, dup_list)))
print(remove_dup)


하지만 이 방법은dup_list=[1, [1, 1], [1, 1]]과 같이 uniterable element이 포함된 list의 경우 tuple()로 변환이 되지 않습니다.

list 이외의 다양한 타입이 있다면, 약간 야매(?) 방법이 있긴 한데..





타입이 다양하면 str으로 변환 해보자.

import ast

def convert_str(x):
	if type(x) is str:
		return "'" + x + "'"
	else:
		return str(x)

dup_list = [1, '1', [2, 3], [2, 3], [2, '3'], {2, 3}, (2, 3)]
remove_dup = list(map(ast.literal_eval, set(map(convert_str, dup_list))))
print(remove_dup)

원소들을 모두 문자열로 바꾼 후 set을 취합니다.

다만, str 타입의 경우에는 앞뒤로 따옴표를 추가해줍니다.

그 후 ast.literal_eval()로 str으로 변환된 것들을 다시 원래 데이터 타입으로 변환해줍니다.

참고로 ast.literal_eval() 함수는 () -> tuple, {} -> set / dict, [] -> list, '' -> str와 같이 변환하는 해줍니다.




실행 결과를 보면 중복이 타입에 따라 제거가 된 것을 확인할 수 있습니다.

사실 이 정도 중복을 제거 해야 하는 일이 많지는 않습니다만 이렇게도 할 수 있다 정도만 참고하시면 될 거 같습니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기 부여가 됩니다.



,
Singleton

싱글톤이란?

하나! 오직 하나!


싱글톤 패턴은 클래스의 인스턴스가 오직 하나임을 보장하는 디자인 패턴이다.

어떤 클래스의 인스턴스를 생성하고 소멸하는 과정이 빈번하게 발생한다면, 고민해도 될 만한 패턴


대표적인 형태 - Eager initialization

public class Singleton{
	// 인스턴스를 Static 생성한다. 
	private static final Singleton instance = new Singleton();

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		return instance;
	}
}


싱글톤 패턴을 사용하는 코드에서 나타나는 특징은 크게 세 가지 입니다.


  1. private로 선언된 생성자

단 하나의 인스턴스를 보장하기 위해서, 클래스 외부에서 새로운 인스턴스의 생성을 막아야 하므로 private로 접근을 제어한다.


  1. Static으로 선언된 인스턴스 멤버 변수

일반적으로 우리가 new 를 사용하여 클래스의 인스턴스를 선언하는 방법을 동적 생성이라고 하는데, 이 때는 메모리 구조상 Heap에 위치하게 된다.

Heap에 있는 인스턴스는 메모리 할당과 해제를 거치게 되는데, 이 연산이 많은 사용자들이 사용하는 서버에서 동시에 이루어 진다면, 부하가 될 수 있다.

반면 Static으로 선언된 인스턴스는 최초 클래스 로드시에 메모리에 적재된 후, 계속 메모리에 남게 된다.


  1. 오직 getInstance() 함수로 Instence 접근

이 클래스의 인스턴스를 접근할 수 있는 곳은 오로지 getInstance() 입니다.


위의 세 가지 조건을 만족한다면 클래스의 인스턴스가 1개인 클래스를 생성가능합니다.

이 방법이 가지는 단점은 이 클래스를 사용하든 안하든 항상 메모리 상에 상주한다는 겁니다.

getInstance() 가 실행 되기 전부터 메모리에 상주하여 낭비를 한다는 것이 마음에 썩 들지는 않습니다.

그래서 클래스를 사용할 때 메모리에 인스턴스를 로드하는 방법을 고안합니다.





Lazy Initialization

public class Singleton{
	// 미리 생성하지 않는다.
	// private static final Singleton instance = new Singleton();
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		// 추가!! 최초로 사용할 때 인스턴스를 생성한다.
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


이전 코드와 비교 하기 위해서 주석으로 이전 코드를 남겨 두었다.


  1. 미리 생성하지 않는다.

클래스의 인스턴스를 사용하기 전부터 메모리에 상주하던 방법이 아닌 최초로 사용하는 곳에서 인스턴스를 생성합니다.

getInstance() 함수가 한 번이라도 실행되어야 이 클래스를


  1. 단 하나의 Instance 보장 문제

우연히 같은 시간에 여럭 개의 쓰레드가 getInstance()를 호출된다면, 하나의 인스턴스를

이 경우에 단 하나의 Instance를 보장 가능하다고 말 할 수 없습니다.

아래의 코드는 Thread-Safe 를 붕괴해보는(?) 소스코드 입니다.


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Runner implements Runnable{
   public static final CyclicBarrier gate = new CyclicBarrier(20);

   @Override
   public void run() {
      try {
         gate.await();
         Singleton s = Singleton.getInstance();
      } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
      }
   }

   public static void main(String[] args) {      
      for(int i=0; i<20; i++) {
         Runner broker = new Runner();
         Thread worker = new Thread(broker);
         worker.start();
      }
   }
}


이 소스 코드는 20개의 쓰레드가 동시에 getInstance() 를 실행하는 소스 코드입니다.

실행 결과를 보면 여러 개의 인스턴스가 생성되는 것을 확인 할 수 있습니다.



Lock을 걸어버리자

public class Singleton{
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 추가!! Lock을 걸어보자
	public static synchronized Singleton getInstance(){
	// public static Singleton getInstance(){
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


synchronized 로 선언된 영역은 Lock을 걸어 주는 역할을 수행하므로 하나의 쓰레드씩 이 함수를 수행하게 되므로 Thread-Safe 문제를 보완해 줍니다.

하지만 우리가 병렬로 수행하기 위한 목적으로 멀티쓰레드를 쓰는데, synchronized 구간에서 병렬성이 무너지기 때문에 성능저하가 발생할 수 있습니다.






holder에 의한 초기화

public class Singleton{
	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}
	
	// Holder Class 추가
	private static class Holder {
		static{
			System.out.println("Holder Class");
		} // Holder 클래스를 메모리에 로드할 때 메세지를 보여주기 위함
		private static final Singleton instance = new Singleton();
	} 	

	public static Singleton getInstance(){
		return Holder.instance;
	}
	// getInstance() 말고 다른 함수를 호출하였을 때 인스턴스 생성 여부 확인용
	public static void print(){
		System.out.println("Singleton.print()");
	}
}


Singleton 클래스 내부에 Holder 클래스를 작성하고 그의 멤버 변수로 Singleton 클래스의 인스턴스를 생성합니다.

앞서 소개한 두 가지 방법에서 발생한 문제점을 어느 정도 보완하는지 체크해 보았습니다.


  1. 사용 전에 미리 생성하지 말 것

getInstance() 호출 전 Singleton 클래스의 인스턴스가 static 맴버 변수로 작성되지 않았으므로 인스턴스가 생성되지 않습니다.

위의 소스코드에서getInstance() 를 실행하지 않고 print() 함수만 실행했을 때 결과를 보면 쉽게 이해하실 수 있을 겁니다.


  1. Thread-Safe 보장

getInstance() 함수가 실행되면 Static으로 선언된 Holder 클래스는 JVM의 Class Loader 에 의해 메모리에 적재됩니다.

Holder는 Singleton 클래스의 인스턴스를 가지고 있으니 Thread-Safe를 붕괴할 수 있는 경우는 Holder 클래스가 Class Loader에 의해 메모리에 적재될 때 중첩되어 Load되는 경우 뿐입니다.

하지만 Class가 초기화되는 과정은 Java Language Specification (JLS)에 의해 시퀀스하게 진행됩니다.

그래서 synchronized로 실행했을 때처럼 Lock을 제공하지만 더 좋은 성능을 보장하는 방법을 제공합니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다.


,



이미 에버노트 앱을 통해서 티스토리에 글을 업로드 할 수 있지만,

에버노트는 마크다운을 지원하지 않아 글을 이쁘게 작성하기가 어려움이 있다.

(사실 에버노트나 티스토리에서 마크다운을 지원하기만 했어도 이런 뻘짓은 안했다)

Bear 앱 유료 구독에는 Html Export 기능이 있는데 이 기능을 사용해서 티스토리 글을 작성하는 삽질의 과정을 소개하고자 합니다.


한 달동안 써본 Bear App 좋은 점

이 글도 Bear 앱으로 작성하였습니다.


베어 앱을 한 달 가까히 쓰면서 느낀 장점들을 간단하게 소개하겠습니다.

오늘은 베어 앱을 활용해서 티스토리 블로그를 작성해보는 것이 목적이니까 간단하게 소개하고 넘어 가고, 계속 사용하면서 느낀 점들을 소개하는 시간을 가져 보도록 하겠습니다.


  1. 마크 다운 문법을 지원한다.
  2. 태그를 지원하여 검색하기가 편하다.
  3. 코드 하이라이팅을 지원한다.
  4. 이쁘다. 마크다운이 적용된 서식들이 이뻐서 글을 작성하는 재미가 있었다.
  5. 몇몇의 마크 다운을 몰라도 단축키가 잘 구성되어 있어서 쓰는데 문제는 없었다.
  6. 유료 버전을 사면 추가적인 테마와 다른 애플 기기랑 연동도 가능합니다.

(물론 필자는 아이패드 뿐이라 해당사항은 없습니다.)


테스트 : 코드 텍스트

import pandas as pd
with open("myfile.txt") as f:
	print(f.readlines())


코드 텍스트의 하이라이팅은 나름 만족스럽네요.


문제점 1 : 사진

HTML 로 Export를 했을 때 문제점은 바로 사진을 함께 Export가 안된다는 점.

아이패드에서 티스토리로 사진을 깔끔하게 올리는 방법은 에버노트를 거쳐서 올리는 방법이 현재로써는 최선으로 느껴진다.


현재는 초안을 아이패드로 작성을 하고 PC에서 사진을 넣어주는 방법을 우선적으로 사용하였습니다.

사진을 넣는 방법을 조금 더 고민을 한 후에 추가하겠습니다.


문제점 2 : 전역 Style

HTML 스타일을 변경하는데 이 스타일이 지역적으로 변경되는 것이 아니라 전역으로 변경되는 문제점이 있었다.

<div> 클래스로 본문이 감싸져 있긴 하지만, CSS Selector 들이 클래스 내부의 요소들이 아닌 전역으로 설정되어 있어서, 블로그 페이지의 다른 요소들에도 스타일이 영향을 받는 것을 확안을 하였다.



나름의 프로세스를 만들어 보았다.

  1. 우선 전역으로 설정된 CSS를 <div> 클래스에 상속하는 태그들에 대하여만 스타일이 적용되게끔 Selector를 변경한 CSS 파일을 만들었습니다.

그리고 해당 CSS 파일을 티스토리 스킨에 반영하는 작업을 수행하였습니다.


bear_markdown.css




스킨 편집화면에서 위의 CSS 파일을 넣어 줍니다.

그리고 HTML 편집 화면에서 아래의 코드를 추가로 넣어줍니다.

<link href="./images/bear_markdown.css" rel="stylesheet">


이 작업은 딱 한 번만 수행하면 되는 작업이라 귀찮지만 조금 참고 진행해보았습니다.


  1. 베어에서 HTML로 Export를 합니다.

어떤 곳으로 보낼 것인가 선택을 하게 되는데 이 때 HTML 코드를 복사하는 기능을 사용하는 겁니다.


HTML로 내보내는 과정

작성하던 글에서 i버튼을 누릅니다.


가장 아래 HTML을 선택합니다.


복사하기를 누릅니다. 




  1. 아이패드에서 티스토리에 접속을 하여 새로운 글을 작성합니다.

이 때 에디터에 HTML 체크 부분을 체크한 후 앞서 복사한 HTML 코드들을 붙여 넣기를 합니다.

붙여넣으면 <HTML>, <Head> 와 같이 글과 무관한 태그들이 붙어 있습니다.




불필요한 이 태그들을 지워주는 작업이 필요한데, Tistory 에디터의 HTML 체크를 해제하였다가 다시 체크를 하면 불필요한 태그들이 사라져 있는 것을 볼 수 있어요.

내부적으로 제거를 해주는 스크립트가 포함된 에디터 덕분에 일은 조금 덜었네요.





  1. 제일 마지막 <sytle> 태그 부분을 모두 제거합니다.

앞서 생성한 CSS 파일 덕분에 해당 부분이 없어도 스타일이 적용되어 올라갑니다.




마치며..

베어에서 글을 작성하는 과정들을 소개드렸는데요.

HTML로 내보내는 기능이 월 구독료 1500원을 지불해야 사용이 가능한 기능이라 사용할지 말지 고민이 될 수도 있겠습니다.

저 역시 고민을 했지만 자기 계발의 결과를 메모하기 위한 용도로 이 앱을 사용하는 중이며, 그 내용을 기반으로 블로그를 작성할 생각이라 1500원은 투자다 생각하고 사용할 예정입니다.

물론 사진을 직접 올리지 못하는 단점이 있지만 계속 삽질하면서 프로세스를 확장해 나갈 계획이다.

부디 티스토리 에디터에서 마크다운을 부디 지원해주어서 이런 삽질 없이 블로그를 작성할 수 있었으면 좋겠습니다.


공감 버튼은 작성자에게 큰 힘이 됩니다


,