블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2018. 11. 25. 18:05 Deep Learning

자연어 처리 강좌를 정리한다. 





개념


- 문장을 분해하여 관계를 만들어 주어야 한다. 

- 데이터를 서로 연결하다보면 2차원 이상으로 발전한다. 

- 자연어는 모델링은 쉽지만, 전처리와 결과의 해석이 중요한다. 결과에 따른 해석이 따라 의미가 틀려진다. 이는 이미지/음성과 틀린 부분이다.

- 음운론, 형태론, 통사론, 의미론, 추리론

- Document -> Tokenizing -> Streaming/Tagging (컴퓨터가 이해하는 방식으로 배치하기) -> 최종 Word2Vec 에서 사용함




Tokenizing


- 구글 Colab을 사용한다.

- Token: document에서 쓸만한 것들로 의미를 같는 문자열 -> Tokenizing

- Colab에서 필요한 모듈 설치하기 in ubuntu

! apt-get update

! apt-get install g++ openjdk-8-jdk 

! pip3  install  nltk konlpy wordcloud matplotlib gensim 


! apt-get install fonts-nanum*

! apt-get install fontconfig

! fc-cache -fv

! cp /usr/share/fonts/truetype/nanum/Nanum* /usr/local/lib/python3.6/dist-packages/matplotlib/mpl-data/fonts/ttf/

! rm -rf /content/.cache/matplotlib/*


- import nltk # Natural Language ToolKit 사용

  + sent_tokenize, word_tokenize

- import re  #정규표현식 사용

// 패키지 사용

text = """갤럭시(GalaxyNote9)노트9. 2018년 08월 폭발적인 인기를 이끌고 있습니다. 

담당자 010-222-9999. 홍보팀 010-8888-9999"""

from nltk import sent_tokenize, word_tokenize, FreqDist

sent_tokenize(text)

tokens = word_tokenize(text)


// 정규표현식

import re

tokenizer = re.compile(r'[가-힣]+')

tokenizer.findall(text)




Stemming & Tagging


순서 Tokenizing -> Stemming -> Tagging을 한다. 일반/평문이 결과값이 잘나오고, 강조문/도치문/압축문은 일반/평문으로 만들어하는게 좋다. 


Stemming: 정규화

- 토큰의 어근/어간 추출

- 가능한 비슷한 의미 형태로 묶어주기 => 원형화한다

   예) 가다, 가니, 가고 => 가- 

         산뜻하다, 산뜻하니, 산뜻하고 => 산뜻하-


Tagging: 문법/Filter

- Token별 높은 확률의 태그(속성값/문법)을 추가 

- 단어의 품사별로 묶음: 동사, 명사, 부사


// 토큰

text = "Don't hesitate to ask question"

from nltk.tokenize import TreebankWordTokenizer

tokenizer = TreebankWordTokenizer()

token = tokenizer.tokenize(text)


// 태그

from nltk import pos_tag

pos_tag(token)


// 한글처리

// 테크

from konlpy.tag import Okt

twitter = Okt()

twitter.pos('서울R&D캠퍼스 수업자료')


//토큰, 스테밍을 같이 처리 stem === stemming

text = "워런 버핏은 삼성전자가 아닌 애플주식을 왜 샀을까"

print(twitter.pos(text, stem="true"))




Word Cloud로 시각화하기


- Visualization (시각화)

- 연설문을 워드클라우드로 표현하기 

// 필요 모듈 설치

! apt-get update

! apt-get install g++ openjdk-8-jdk 

! pip3  install  nltk konlpy wordcloud matplotlib gensim 


! apt-get install fonts-nanum*

! apt-get install fontconfig

! fc-cache -fv

! cp /usr/share/fonts/truetype/nanum/Nanum* /usr/local/lib/python3.6/dist-packages/matplotlib/mpl-data/fonts/ttf/

! rm -rf /content/.cache/matplotlib/*

speech_text = "https://raw.githubusercontent.com/YongBeomKim/nltk_tutorial/master/data/pyongyang_fin.txt"

script_text = "https://raw.githubusercontent.com/YongBeomKim/nltk_tutorial/master/data/movie_memories_of_murder_2003.txt"

font_file = "/usr/local/lib/python3.6/dist-packages/matplotlib/mpl-data/fonts/ttf/NanumGothicCoding.ttf"


// 워드클라우드 사용

import requests

import pandas as pd

import matplotlib.pyplot as plt

from wordcloud import WordCloud

from nltk import FreqDist

from nltk.tokenize import word_tokenize

import nltk

nltk.download('punkt')


texts = requests.get(speech_text).text

texts[:100]


// 그리기 

%matplotlib inline

wcloud = WordCloud(font_file).generate(texts)

plt.figure(figsize=(12,12))

plt.imshow(wcloud)

plt.axis('off')


명사만을 사용하여 word cloud 표현

from konlpy.tag import Okt

twitter = Okt()

tokens = twitter.pos(texts, stem=True)

tokens_noun = [token[0] for token in tokens if token[1] == 'Noun']

texts_noun = " ".join(tokens_noun)

texts_noun[:300]




Deep Learning 수행


Natural Language Processing + Deep Learning을 결합하여 챗봇을 만들어가기

  - RNN, LSTM, GRU가 챗봇만드는 기본 알고리즘

  - Data 입력/전처리 (Train Data) -> Cell 선택 및 모델 구성 -> Training / Modeling / Validation -> Model 평가 

  - Input -> Hidden Layer -> Output

  - Input Data 를 구성하는 Word2Vec


Word2Vec는 문장에 따라 데이터를 구조화한다. 

  - 하나의 중심데이터와 주변데이터로 구분함. 

  - 단어간 유사도 검사

  - 구조자체가 작아서, 딥러닝이라 할 수 없다. 

  - 로지스틱 회귀식(2 Cell)을 통해 분류한다. 

  - Corpus (말뭉치) -> Vocabulary Builder -> Context Builder -> Input -> Output 

  


새로운 방식

  - Input 하나에 Output 여러개를 통해 학습 => Skip-gram (sg)

  - try -> error를 통해 맞는 길로 찾아는 방식

과거 방식

  - Input 여러개에 대한 Output 으로 중심 단어 하나 => CBOW (Content Back of Word)


     



실습


  - 20차원의 구조를 만든다. 

  - Dimension(차원)안에 토큰을 넣는다. 비슷한 토큰은 같은 차원에 넣는다. 

  - 빈도수가 적은 것은 하나의 차원에 묶는다. 이안에 빈도수가 높은 것은 중심축에 놓는다. 

    



- size: 차원 30개 (중요)  <== 실습 단어가 151개여서 유의미화를 위해 30차원으로 줄여서 설정했음.

- window: 주변 데이터

- min_count: 10번이상 등장한 단어만 (중요)

- hs: ?

- workers: cpu parallel 동작

- sg: skip-gram

// 전처리후 

...

// 명사 저장

sentences = txtnoun(sentences, skip=skips, tags=["Noun"])

script_file = 'scripts.txt'

with open(script_file, 'w', encoding="utf-8") as file:

   file.write(sentences)


// gensim 이용 모델 만들기

%%time

from gensim.models import word2vec

data = word2vec.LineSentence(script_file)

model = word2vec.Word2Vec(data, size=30, window=2, min_count=10, hs=1, workers=4, iter=100, sg=1)

model_file = "script.model"

model.save(model_file)


결과

CPU times: user 1.59 s, sys: 113 ms, total: 1.71 s, Wall time: 1.7 s


// 모델의 단어수 

model = word2vec.Word2Vec.load(model_file)

len(model.wv.vocab.keys())

list(model.wv.index2word)


결과

151 


// 근접 백터값 확인하기, 참깨밭을 제거하고 추출해 봄

model.wv.most_similar(['현장','백광호'], negative=['참깨밭'], topn=20)


30차원을 시각화하기 위해 skitlearn의 TSNE를 이용해서 2차원으로 변경해서 표현한다. 




<참조>

- 강좌 슬라이드, 전체 실습 파일

- 정규 표현식 정리, 실습하는 서비스

KoNLP

- Word2Vec 관련 이론

posted by Peter Note
2018. 11. 22. 14:37 Meteor/React + Meteor

Meteor의  accounts-password를 패키지를 통한 로그인 환경 만들기를 해본다. 


  - accounts-password 패키지를 통한 미티어 사용자 가입/로그인 사용 방법 알기

  - React Router 설정

  - 미티어의 Tracker.autorun을 통해 reactivity computation과 resource 사용 방법 알기

  - 소스코드




미티어 사용자 관리


accounts-password 패키지를 설치하고, 가입 및 로그인 화면을 만든다. 

$ meteor add accounts-password

$ meteor npm install --save bcrypt

// Link 사용을 위해 

$ meteor npm install --save react-router-dom


Signup.tsx와 Login.tsx 생성한다. 

  - email, password의 값을 읽기 위하여 typescript방식의 public refs를 정의한다. 

  - Accounts.createUser를 통해 사용자를 등록한다.

// Singup.tsx

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Accounts } from 'meteor/accounts-base'


export interface SignupProps {}

export default class Signup extends React.Component<SignupProps, any> {

  // @see https://goenning.net/2016/11/02/strongly-typed-react-refs-with-typescript/

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onCreateAccount = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Accounts.createUser({email, password}, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Signup to short Link</h1>

        { this.state.error ? <p>{this.state.error} </p> : undefined}

        <form onSubmit={this.onCreateAccount}> 

          <input type="email" ref="email" name="email" placeholder="Email"/>

          <input type="password" ref="password" name="password" placeholder="Password"/>

          <button>Create Acount</button>

        </form>

        <Link to="/login">Already have a account?</Link>

      </div>

    );

  }

}


Login.tsx

  - refs 정의

  - Meteor.loginWithPassword를 통해 로그인한다.

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Meteor } from 'meteor/meteor';


export interface LoginProps {

  history: any;

}

export default class Login extends React.Component<LoginProps, any> {

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

        {this.state.error ? <p>{this.state.error} </p> : undefined}

        <form onSubmit={this.onLogin}>

          <input type="email" ref="email" name="email" placeholder="Email" />

          <input type="password" ref="password" name="password" placeholder="Password" />

          <button>Login</button>

        </form>

        <Link to="/signup">Have a account?</Link>

      </div>

    );

  }

}


NotFound.tsx 파일도 생성한다. 

import * as React from 'react';

export default () => <div>Not Found Page</div>




React Router 설정


라우팅 설정을 통해 가입, 로그인후 Link화면으로 이동하는 설정한다. 


history 모듈을 설치한다.

$ meteor npm install --save history


client/main.tsx에 Route설정을 한다.

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';

import { Route, Router, Switch } from 'react-router-dom';

import { createBrowserHistory } from 'history';

 

import App from '../imports/ui/App'

import Login from '../imports/ui/pages/Login';

import Signup from '../imports/ui/pages/Signup';

import InfoContainer from '../imports/ui/Info';

import NotFound from '../imports/ui/pages/NotFound';

import store from '../imports/ui/store';


const browserHistory = createBrowserHistory();

const Root = (

  <Provider store={store}>

    <Router history={browserHistory}>

      <Switch>

        <Route exact path="/" component={Login} />

        <Route path="/main" component={App} />

        <Route path="/signup" component={Signup} />

        <Route path="/links" component={InfoContainer} />

        <Route path="*" component={NotFound} />

      </Switch>

    </Router>

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


links는 인증된 사용자만 볼 수 있으므로 Tracker.autorun이라는 Reactivity Computation 영역에서 Meteor.user()라는 Reactivity Source가 로그인 상태인지 검사한다. 

  - 로그인 상태가 아니면 무조건 login화면으로 이동

  - 로그인 상태이면서 인증이 필요없는 화면인 경우 link화면으로 이동

// client/main.tsx 

import { Tracker } from 'meteor/tracker';


Tracker.autorun(() => {

  const isAuthenticated = !!Meteor.user();

  if (isAuthenticated) {

    const pathname = browserHistory.location.pathname;

    const unauthenticatedPages: any = ['/', '/signup'];

    const isUnauthenticatedPage = unauthenticatedPages.includes(pathname);

    if (isUnauthenticatedPage) {

      browserHistory.push('/links'); // main

    } else {

      browserHistory.push(pathname);

    }

  } else {

    browserHistory.push('/'); // login

  }

});


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


imports/ui/Info.tsx 에 logout 기능 추가

...

import { Accounts } from 'meteor/accounts-base';


interface InfoProps {

  links: any;

  loading: boolean

}


class Info extends React.Component<InfoProps, any> {


  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  onLogout = () => {

    Accounts.logout();

  }


  render() {

    return (

      <div>

        <button onClick={this.onLogout}>Log out</button>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}




가입 및 로그인 테스트 확인하기


가입을 하고, 로그인을 한다. 

Chrome Extension 으로 MiniMongoExplorer를 설치하고 user Collection의 내용을 확인한다. 


또는 Chrome DevTool의 Application에서 로그인 사용자의 userId와 console에서 확인해 볼 수 있다. 또한 Link페이지에서 로그아웃을 해보고 콘솔을 확인해 본다. 




<참조>

- 크롬 확장툴 MiniMongoExplorer

- 세번째 글 소스

posted by Peter Note
2018. 11. 16. 16:07 Meteor/React + Meteor

Meteor에서 Redux 환경을 설정하고, 애플리케이션에 적용해 본다.


  - Meteor Collection의 Pub/Sub 설정

  - Collection의 CRUD 서비스 클래스 개발

  - Redux 환경 설정

  - Redux의 action, reducer 개발 및 store 설정

  - 애플리케이션에 Redux action 호출하기

  - 소스코드 




MongoDB Collection Pub/Sub 설정


기본설치를 하게 되면 autopublish 미티어 패키지가 설치되어 있다. 삭제한다.

$ meteor remove autopublish


삭제를 하면 links 컬렉션으로 부터 데이터를 받지 못한다. 이제 필요한 것을 받기 위해 publish, subscribe를 설정한다. 

  - server/main.tsx에서 insertLink 코드를 제거한다.

  - server/main.tsx에서 links 코드 import 문을 수정한다. 

// main.tsx 전체코드

import { Meteor } from 'meteor/meteor';

import '../imports/api/links';  // 서버에서 사용하기 위해 반드시 import해야 한다. 하지 않으면 collection 생성, 제어를 못 한다.


Meteor.startup(() => { }); // 텅빈 코드 블록


  - imports/api/links.ts 에 publish 한다. 

if (Meteor.isServer) {

  Meteor.publish('links', () => Links.find());

}


  - subscribe를 사용할 때 미티어의 Tracker.autorun을 사용하지 않고, React의 props에 subscribe정보를 바로 맵핑해 주는 withTracker를 사용하기 위해 react-meteor-data 패키지를 사용한다.

$ meteor npm install --save react-meteor-data


  - imports/api/ui/Info.tsx에 withTracker를 설정한다. 

import { Meteor } from 'meteor/meteor';

import Links from '../api/links';


interface InfoProps {

links: any;

loading: boolean;

}


// 맨 하단

export default withTracker(() => {

       const handle = Meteor.subscribe('links');

return {

links: Links.find().fetch(),

loading: !handle.ready()

}

})(Info);




MongoDB Collection CRUD


간단한 Link CRUD 애플리케이션을 만든다. 


  - Collection의 CRUD 로직을 담은 service를 생성한다. imports/ui밑에 link폴더를 생성한다. 

// imports/ui/link/link.service.ts

import Links from '../api/links';


class LinkService {

  addLink({title, url}) {

    Links.insert({title, url, createAt: new Date()});

  }


  removeLink(_id: string) {

    Links.remove(_id);

  }


  updateLink(_id: string, {title, url}) {

    Links.update(_id, {title, url, updateAt: new Date()});

  }

}


export const linkService = new LinkService();


  - imports/ui/link/AddLink.tsx 파일 추가

import * as React from 'react';

import { linkService } from './links.service';

export interface AddLinkProps {

}

export default class AddLink extends React.Component<AddLinkProps, any> {

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    linkService.addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


  - imports/ui/App.tsx에서 <Info/> 만 남기고 삭제하고, Info.tsx에 AddLink를 추가한다. 

// App.tsx

class App ... {

  render() { 

    return (

      <div></InfoContainer/></div>

    )

  }

}


// Info.tsx
class Info ... {
  render() {
    return(
      <div> 
          <AddLink />
          ....
       </div>
    )
  }
}


  - link폴더 밑에 LinkList.tsx와 Link.tsx를 추가한다. 

// Link.tsx

import * as React from 'react';

export interface LinkProps {

  link: any

}


export default class Link extends React.Component<LinkProps, any> {

  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

      </li>

    );

  }

}


// LinkList.tsx

import * as React from 'react';

import Link from './Link';

export interface LinkListProps {

  links: any[]

}


export default class LinkList extends React.Component<LinkListProps, any> {

  public render() {

    const links = this.props.links.map(

      link => <Link link={link}/>

    );

    return (

      <div>

        <h2>Links</h2>

        <ul>{links}</ul>

      </div>

    );

  }

}



  - Info.tsx에서 LinkList를 사용토록 변경한다. 

class Info extends React.Component<InfoProps, any> {

  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  render() {

    const { links } = this.props;

    return (

      <div>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}


  - 마지막으로 link 삭제를 한다. 

// imports/ui/link.Link.tsx

import * as React from 'react';

import { linkService } from './links.service';


export interface LinkProps {

  link: any

}


export default class Link extends React.Component<LinkProps, any> {

  removeLink = () => {

    const { link } = this.props;

    linkService.removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}





Redux 관련 패키지 설치


redux 기본 패키지를 설치한다. 

$ meteor npm install --save redux react-redux react-router-redux

$ meteor npm install --save-dev @types/react-redux  @types/react-router-redux


redux action, reducer을 보다 편하게 사용할 수 있는 typesafe-actions 모듈을 설치한다.

$ meteor npm install --save typesafe-actions 


redux에서 비동기를 처리할 수 있는 redux-observable 과 rxjs 모듈을 설치한다. 

$ meteor npm install --save redux-observable rxjs


state 갱신으로 인한 반복 rendering을 제거해 성능향상을 위한 reselect 모듈도 설치한다. 

$ meteor npm install --save reselect




Redux 코드 개발


link action 개발

  - action type constants 정의

  - action Model 정의

  - action  에 대한 정의

// imports/ui/pages/link/link.action.ts

import { action } from 'typesafe-actions';


/**************************

 * constants, model

 **************************/

// constants

export const ADD_REQUEST = '[link] ADD_REQUEST';

export const ADD_SUCCESS = '[link] ADD_SUCCESS';

export const ADD_FAILED = '[link] ADD_FAILED';

export const DELETE_REQUEST = '[link] DELETE_REQUEST';

export const DELETE_SUCCESS = '[link] DELETE_SUCCESS';

export const DELETE_FAILED = '[link] DELETE_FAILED';

export const CHANGE = '[link] CHANGE';


// model

export type LinkModel = {

  _id?: string;

  title?: string;

  url?: string;

  visited?: boolean;

  error?: boolean;

  errorMsg?: any;

  success?: boolean;

  createdAt?: any;

  updatedAt?: any;

};


/**************************

 * actions, action-type

 **************************/

export const addLink = (params: LinkModel) => action(ADD_REQUEST, params);

export const addLinkSuccess = (params: LinkModel) => action(ADD_SUCCESS, params);

export const addLinkFailed = (params: LinkModel) => action(ADD_FAILED, params);

export const removeLink = (id: string) => action(DELETE_REQUEST, id);

export const removeLinkSuccess = (id: string) => action(DELETE_SUCCESS, id);

export const removeLinkFailed = (id: string) => action(DELETE_FAILED, id);

export const changeLink = (id: string) => action(CHANGE, id);


link reducer 개발

  - Link State 정의

  - LinkAction 타입 

  - Link reducer 분기

  - reselect를 이용한 selector는 별도로 만들지 않고, link.reduer.ts 파일이 함께 둔다. (파일이 너무 많아져서...)

// imports/ui/pages/link/link.reducer.ts

import { ActionType } from 'typesafe-actions';

import { combineReducers } from 'redux';

import { createSelector } from 'reselect';

import * as actions from './link.action';


/**************************

 * state

 **************************/

// state

export type LinkState = {

  list: actions.LinkModel[],

  linkFilter: string

};


/**************************

 * reducers

 **************************/

export type LinkAction = ActionType<typeof actions>;

export const linkReducer = combineReducers<LinkState, LinkAction>({

  list: (state = [], action) => {

    switch (action.type) {

      case actions.ADD_FAILED:

        return state;

      case actions.ADD_SUCCESS:

        return [...state, action.payload];

      case actions.DELETE_FAILED:

        return state;

      case actions.DELETE_SUCCESS:

        return state.filter(item => item._id === action.payload);

      case actions.CHANGE:

        return state.map(

          item =>

            item._id === action.payload

              ? { ...item, visited: !item.visited }

              : item

        );

      default:

        return state;

    }

  },

  linkFilter: (state = '', action) => {

    switch (action.type) {

      case actions.CHANGE:

        if (action.payload === 'visited'){

          return '';

        } else {

          return 'visited';

        }

      default:

        return state;

    }

  }

})


/**************************

 * selectors

 **************************/

export const getLinks = (state: LinkState) => state.list;

export const getLinkFilter = (state: LinkState) => state.linkFilter;

export const getFilteredLinks = createSelector(getLinks, getLinkFilter, (links, linkFilter) => {

  switch (linkFilter) {

    case 'visited':

      return links.filter(t => t.visited);

    default:

      return links;

  }

});


redux-observable을 이용한 epic을 만든다.

  - async 처리

  - rxjs 이용

  - takeUntil은 cancel을 위한 장치이다.

// imports/ui/pages/link/link.epic.ts

import { Epic, combineEpics } from 'redux-observable';

import { filter, map, switchMap, takeUntil } from 'rxjs/operators';

import { isOfType } from 'typesafe-actions';


import Links from '../../../api/links';

import { insertCollection, removeCollection, RequestModel } from '../../sdk';

import * as actions from './link.action';


const addLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.ADD_REQUEST)),

    switchMap(action => {

      const { title, url } = action.payload;

      return insertCollection(Links, { title, url, createdAt: new Date() })

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.addLinkFailed({ ...response.result })

      }

      return actions.addLinkSuccess(response.result)

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


const removeLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.DELETE_REQUEST)),

    switchMap(action => {

      return removeCollection(Links, action.payload);

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.removeLinkFailed({ ...response.result, ...response.params })

      }

      return actions.removeLinkSuccess(response.params._id);

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


export const linkEpic = combineEpics(addLink, removeLink);


Meteor Client Collection 을 Observable로 전환

  - meteor client collection의 insert, remove시에 call 결과를 Observable로 변환하여 반환하는 유틸을 만든다. 

  - 이는 redux-observable의 epic에서 async 데이터를 switchMap 오퍼레이터로 다루기 위함이다. 

// imports/sdk/util/ddp.util.ts

import { from, Observable } from 'rxjs';


export type RequestModel = {

  error?: boolean;

  success?: boolean;

  result: any;

  params?: any;

}


export function insertCollection(collection: any, params: any): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.insert(params, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { ...params } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: {...params} });

      } else {

        resolve({ success: true, result: { ...result }, params: { ...params } });

      }

    });

  }));

}


export function removeCollection(collection: any, _id: string): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.remove(_id, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { _id } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: { _id } });

      } else {

        resolve({ success: true, result: { ...result }, params: { _id } });

      }

    });

  }));

}




RootReducer와 Store 설정


link관련 action, reducer, epic 개발이 끝나면 이를 등록하는 설정을 한다. 

  - root Reducer 정의

  - root State  타입 정의

  - root Action 타입 정의

  - root Epic 정의

  - root Store 설정

// imports/ui/store.ts

import { combineEpics, createEpicMiddleware } from 'redux-observable';

import { createStore, applyMiddleware, combineReducers, compose } from 'redux';

import { RouterAction, LocationChangeAction } from 'react-router-redux';

import { routerReducer } from 'react-router-redux';

import { StateType } from 'typesafe-actions'; 


import { linkEpic } from './pages/link/link.epic';

import { linkReducer, LinkAction } from './pages/link/link.reducer';


/***********************

 * root reducer

 ***********************/

const rootReducer = combineReducers({

  router: routerReducer,

  links: linkReducer

});


/***********************

 * root state

 ***********************/

export type RootState = StateType<typeof rootReducer>;


type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction = ReactRouterAction | LinkAction;


/***********************

 * root epic

 ***********************/

const composeEnhancers =

  (process.env.NODE_ENV === 'development' &&

    (window as any) &&

    (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||

  compose;


const rootEpic = combineEpics(linkEpic);

const epicMiddleware = createEpicMiddleware();


/***********************

 * root store

 ***********************/

function configureStore(initialState?: object) {

  // configure middlewares

  const middlewares = [epicMiddleware];

  // compose enhancers

  const enhancer = composeEnhancers(applyMiddleware(...middlewares));

  // create store

  const store = createStore(rootReducer, enhancer);

  // run epic: https://redux-observable.js.org/docs/basics/SettingUpTheMiddleware.html

  epicMiddleware.run(rootEpic);

  return store;

}


const store = configureStore();

export default store;


애플리케이션에 store를 최종 설정한다. 

// client/main.tsx

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';


import App from '../imports/ui/App'

import store from '../imports/ui/store';


const Root = (

  <Provider store={store}>

    <App />

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


window 객체 인식을 위해 type definition을 정의한다. 

  - 루트에 typings 폴더 생성

  - typings/index.d.ts 파일 생성

declare var window: any;




Application에서 Redux Action 호출하기


add/remove link 리덕스 액션을 애플리케이션에 적용한다. link.service.ts 는 사용하지 않는다. 

  - AddLink.tsx에 addLink 액션 적용

// imports/ui/link/AddLink.tsx

import * as React from 'react';

import { connect } from 'react-redux';

import { addLink } from './link.action';


export interface AddLinkProps {

  addLink: Function;

}


class AddLink extends React.Component<AddLinkProps, any> {

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    const { addLink } = this.props;

    addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


export default connect(undefined, { addLink })(AddLink);


  - Link.tsx에 removeLink 액션 적용

// imports/ui/pages/link/Link.tsx

import * as React from 'react';

import { removeLink } from './link.action';

import { connect } from 'react-redux';


export interface LinkProps {

  link: any,

  removeLink: Function

}

class Link extends React.Component<LinkProps, any> {

  removeLink = () => {

    const { link, removeLink } = this.props;

    removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}


export default connect(undefined, { removeLink })(Link);





<참조>

- 블로그 소스 코드

react-redux-typescript-guide 소스

- redux-actions 사용하기

- redux-observable 사용하기

- typesafe-actions 사용하기

- reselect 사용하여 성능 최적화 하기

posted by Peter Note
2018. 11. 13. 16:42 Meteor/React + Meteor

Meteor v1.8이 되면서 CLI에 react기반 애플리케이션 자동 생성 기능이 추가되었다. 여기에 Typescript와 Redux을 접목해서 개발환경을 꾸며 본다. 


  - React기반 Meteor 애플리케이션 생성

  - SCSS 환경설정

  - Typescript 환경설정

  - 미티어의 Reactivity Computation인 withTracker 확인

  - AntDesign CSS Framework 설정

  - 소스코드




React 기반환경 만들기 


meteor를 설치한다. 

// Node Version Manager를 통해 Node LTS 버전을 설치한다. 최신 Meteor 1.8 은 Node v8.*을 사용한다.

$ nvm install 8.12.0


// meteor를 설치한다. 

$ curl https://install.meteor.com | sh


// 향후 meteor v1.8.1 버전이 릴리즈 되면 성능향상을 위해 업그레이드를 반드시 수행한다. 

$ meteor update --release 1.8.1


애플리케이션 생성

// 명령어: meteor create --react <AppName>

$ meteor create --react react-first

$ cd react-first

$ meteor run




SCSS 환경 전환


.css파일을 .scss파일로 전환한다. 이를 지원하기 위해 scss 패키지를 설치한다. 

$ meteor add fourseven:scss




Typescript 관련 모듈 설치


meteor의 typescript 지원 패키지를 설치한다.

$ meteor add barbatus:typescript


@types와 필요 모듈을 설치한다.

$ meteor npm install --save-dev @types/meteor 

$ meteor npm install --save-dev @types/node

$ meteor npm install --save-dev @types/react

$ meteor npm install --save-dev @types/react-dom


$ meteor npm install --save-dev typescript

$ meteor npm install --save-dev tslint

$ meteor npm install --save-dev tslint-react




Typescript 환경 전환


root에 tsconfig.json 파일 추가

{

    "compilerOptions": {

        "target": "es6",

        "allowJs": true,

        "jsx": "preserve",

        "moduleResolution": "node",

        "types": [

            "node"

        ]

    }

}


root에 tslint.json 파일 추가 

// 과도한 lint가 귀찮아 extends는 주석처리함 

{

    // "extends": [

    //     "tslint:latest",

    //     "tslint-react"

    // ],

    "rules": {

        "quotemark": [

            true,

            "single",

            "jsx-double"

        ],

        "ordered-imports": false,

        "no-var-requires": false

    }

}


root의 client와 imports, server 폴더안의 파일 명칭 .jsx / .js 를 .tsx / .ts 로 변경한다. tests 폴더는 .js 그대로 둔다.



마직막으로 package.json에서 mainModule의 client를 main.tsx로 수정한다. 





.tsx 파일 내용을 typescript에 맞게 수정하기


1) import 문 수정

// client, imports 폴더안의 모든 .tsx 파일


// react import

import React from 'react'; ==> import * as React from 'react';


// 절대경로를 상대경로로 수정

import App from '/imports/ui/App'; ==> import App from '../imports/ui/App';


// .jsx 파일을 확장자 제거 App.tsx

import Hello from './Hello.jsx' ==> import Hello from './Hello';

import Info from './Info.jsx' ==> import Info from './Info';


2) class 문 수정

class Hello extends Components {  ==> class Hello extends React.Component {


3) class에 Props 타입 설정

// info.tsx


interface InfoProps {

  links: any;

}


class Info extends React.Component<InfoProps, any> {

  ...

}


4) export default 문 수정

    withTracker는 Meteor의 Reactivity Computation 공간이다. Computation에는 Meteor의 Reactivity Resource가 위치한다. 즉, source가 변경되면 Computation영역이 재실행된다. 

// imports/ui/info.tsx 

export default InfoContainer = withTracker(...)

   ==> 

export default withTracker(...);


// imports/api/links.ts

export default Links = new Mongo.Collection('links');

   ==>

const Links = new Mongo.Collection('links');

export default Links;


이제 다시 meteor run 을 실행한다. 



Ant Design 설치하기


CSS Component를 제공하는 antd를 설치한다. 

$ meteor npm install --save antd

$ meteor npm install --save-dev @types/antd


client/main.tsx 안에 antd css 를 import 한다. 

import '../node_modules/antd/dist/antd.css';


또는, antd.less 파일을 직접 import할 수 있다. 

$ meteor add less

$ meteor npm install --save indexof


// client/theme.less 파일 생성하고 antd.less 파일 import하기

@import '{}/node_modules/antd/dist/antd.less';


antd의 Menu를 App.tsx에 적용해 본다. 

import * as React from 'react';

import Hello from './Hello';

import Info from './Info';

import { Menu, Icon } from 'antd';


const SubMenu = Menu.SubMenu;

const MenuItemGroup = Menu.ItemGroup;


class App extends React.Component {

  state = {

    current: 'mail',

  };


  handleClick = (e) => {

    console.log('click ', e);

    this.setState({

      current: e.key,

    });

  }


  render() {

    return (

      <div>

        <Menu

          onClick={this.handleClick}

          selectedKeys={[this.state.current]}

          mode="horizontal"

        >

          <Menu.Item key="mail">

            <Icon type="mail" />Navigation One

            </Menu.Item>

          <Menu.Item key="app" disabled>

            <Icon type="appstore" />Navigation Two

            </Menu.Item>

          <SubMenu title={<span className="submenu-title-wrapper"><Icon type="setting" />Navigation Three - Submenu</span>}>

            <MenuItemGroup title="Item 1">

              <Menu.Item key="setting:1">Option 1</Menu.Item>

              <Menu.Item key="setting:2">Option 2</Menu.Item>

            </MenuItemGroup>

            <MenuItemGroup title="Item 2">

              <Menu.Item key="setting:3">Option 3</Menu.Item>

              <Menu.Item key="setting:4">Option 4</Menu.Item>

            </MenuItemGroup>

          </SubMenu>

          <Menu.Item key="alipay">

            <a href="https://ant.design" target="_blank" rel="noopener noreferrer">Navigation Four - Link</a>

          </Menu.Item>

        </Menu>

        <Hello />

        <Info />

      </div>

    );

  }

}


export default App;




지금까지의 적용 소스코드, Redux 환경 설정하기는 다음 글에서..




<참조>


- meteor-react-typescript 코드 샘플

- meteor changelog

- meteor에 antd .less 파일 import하기 (데모소스)

- meteor-react-typescript-boilerplate 소스

posted by Peter Note
2018. 10. 23. 16:52 BlockChain

Ethereum 의 Mining 부분의 동작원리 발표를 정리한다.



Ethereum 

  - P2P Network + Mining의 결합 

  - Mining



소스 설명

  - miner/miner.go

  - worker가 대부분의 mining 로직을 처리한다.

  - new worker가 실행되면 4가지 루프가 go routine 기반으로 계속 돈다. 

  - 발표 자료: 

3. How does go-ethereum work__Aiden.pptx





참조

https://steemit.com/@sigmoid sigmoid 님의 go-ethereum 분석기 : geth의 거의 모든 부분을 잘 정리해주셨습니다. 개인적으로 정말 많은 도움이 되었습니다.

https://github.com/NAKsir-melody/go-ethereum sigmoid 님의 go-ethereum 주석 한글화 프로젝트

http://www.notforme.kr/block-chain/geth-code-reading 자바 개발자의 go-ethereum 소스 분석기 : geth의 entry point부터 차근차근 잘 설명해주셨습니다. 처음 공부를 시작하시는 분들은 이 분의 글을 먼저 보시면 큰 도움이 되실겁니다.

https://blog.seulgi.kim/ 코드박스 김슬기님의 블로그 : 이더리움에 대한 수준 높은 포스트가 정말 많습니다. 직접적으로 소스코드를 다루는 글은 없지만 이더리움의 기술적인 부분을 이해하시는데 정말 큰 도움이 됩니다.

https://tech.etherstudy.net/ethereum/geth/delve/debug/consensus/lifecycle/2018/08/02/geth-consensus-lifecycle-debug.html 임완섭님의 이더리움 컨센서스 라이프사이클 디버깅 : 디버깅을 통해 소스코드를 보다 더 이해하기 쉽게 해주셨습니다. 역시 큰 도움이 되실겁니다.


'BlockChain' 카테고리의 다른 글

[W3C] 2018 참관기  (0) 2018.11.26
[BlockChain] Onther DevCon 1 - Etherum 2.0 개요  (0) 2018.10.23
posted by Peter Note