Android 6.0 이상에서의 백그라운드 서비스 생존 방법에 대한 고찰


개요

문제점

  • Android 6.0에서의 Doze 모드
  • 삼성 스마트 매니저

전략

  • Wakelock
  • Alarm Manager 방식
  • Google Cloud Message 방식
  • Screen ON

화면 항상 켜기

<RelativeLayout 
    ....
    android:keepScreenOn="true"
    ....
>
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

Wakelock

<uses-permission android:name="android.permission.WAKE_LOCK" />

Wakelock 생성 방법

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        "MyWakelockTag");
wakeLock.acquire();

Broadcast Receiver Wakelok 방법

 WakefulBroadcastReceiver,

https://developer.android.com/training/scheduling/wakelock.html#wakeful

Scheduling Repeating Alarms

삼성 스마트 매니저 극복 방법

http://gun0912.tistory.com/64
http://blog.soundl.ly/2016/04/blog-post_12.html


SQLite 탐색기 및 기초 쿼리


sqlite-browser는 쉽게 구할 수 있지만 생각보다 query 실행시 멈침 현상이 심하다.
검색 도중 python flask로 구현된 web-based 도구가 있어서 이것을 사용 한다.
sqlite-web이며 설치화 실행 모습은 아래와 같다.

sqlite-web tool

$ sudo apt-get install python-pip ## pip를 이미 설치하였다면 skip 합니다.
$ sudo pip install --upgrade pip  ## pip 업그레이드 합니다.
$ sudo apt install python-setuptools ## sqlite-web설치를 위해 필요
$ sudo pip install sqlite-web     ## sqlite-web 설치 합니다.
## 포트번호 3000(기본값은 8080)으로 현재 디렉토리의 test.db 파일을 웹으로 보고싶은 경우 아래 명령과 같이 입력합니다.
## 만약 test.db파일이 없으면 새로 생성하게 됩니다.
$ sqlite_web -p 3000 ./test.db ## 하이픈 아니고 언더바임에 주의하세요. 
 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
 127.0.0.1 - - [20/Sep/2016 16:25:24] "GET / HTTP/1.1" 200 -
 Created new window in existing browser session.

실행

select

select * from log oder by _id desc limit 20
SELECT Region, count(*)
FROM item
WHERE Region is not null
GROUP BY Region

실행 결과

Region, count
Denmark, 4
Sweden, 1
USA, 10

하위 20개 선택

SELECT *
FROM "log"
order by _id DESC limit 20



postman 크롬 앱

https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en


사용법

https://stackoverflow.com/questions/16015548/tool-for-sending-multipart-form-data-request


- 멀티 파트 전송도 가능하다. 

두 개 데이터 프레임 병합, 빼기


공통 키를 기준으로 병합

하나이상의 key를 가지고 두 개의 데이터 프레임을 join하는 연산

# merge two data frames by ID
total <- merge(data frameA,data frameB,by="ID")

두 개의 데이터로 병합

# merge two data frames by ID and Country
total <- merge(data frameA,data frameB,by=c("ID","Country"))

빼기

Larger - Smaller

small

     ID       CSF1PO CSF1PO.1 D10S1248 D10S1248.1 D12S391 D12S391.1
203079_BA_M     10       11       14         16      -9        -9
203079_BA_F      8       12       14         17      -9        -9
203080_BA_M     10       12       13         13      -9        -9

big

      ID      CSF1PO CSF1PO.1 D10S1248 D10S1248.1 D12S391 D12S391.1
203078_MG_M     -9       -9       15         15      18        20
203078_MG_F     -9       -9       14         15      17        19
203079_BA_M     10       11       14         16      -9        -9
203079_BA_F      8       12       14         17      -9        -9
203080_BA_M     10       12       13         13      -9        -9
203080_BA_F     10       11       14         16      -9        -9
203081_MG_M     10       12       14         16      -9        -9
203081_MG_F     11       12       15         16      -9        -9
203082_MG_M     11       11       13         15      -9        -9
203082_MG_F     11       11       13         14      -9        -9

Solution

BigDF[ !(BigDF$ID %in% SmallDF$ID), ]

'AI > R Basic' 카테고리의 다른 글

Jupyter Notebook으로 R 실행  (2) 2018.04.15
Data Transformation: dplyr package  (0) 2018.04.14
R Factor 검색  (0) 2017.11.13
RStudio v1.1 Released  (0) 2017.10.10
rJava load 에러  (0) 2017.04.16


  1. Powerpoint 2013을 실행합니다.
  2. [파일 - 옵션]을 클릭합니다.
  3. [언어 교정 – 자동 고침 옵션 – 자동 고침 – 자동으로 맞춤법 검사기의 추천 단어를 사용]체크를 해제합니다.


2018년에서 바라본 딥러닝의 비판적 평가


  • data hungry

    • 데이터가 매번 너무 많이 필요하다. 세상의 모든 문제를 무한한 데이터가 제공된다고 가정하고 풀 수는 없다.
  • shallow & limited capacity for transfer

    • deep은 아키텍쳐가 깊은거지 딥하게 이해 하는 것 아니다.
    • 너무 트레이닝에 오버핏 된다.
    • adversarial하게 학습 모델을 저격하는 데이터 예제를 쉽게 만들 수 있다.
  • no natural way to deal with hieracyhical strcture

    • 맨 마지막의 featrue는 너무 flatten 하다.
    • 지식은 계측정 관계가 있는 딥러닝은 그렇지 못하다
  • struggled with open-ended inference (개방향 문제에 약함)

    • VQA도 그냥 호랑이 어디, 호랑이는 몇마리? 이런건 잘함
    • open-ended inference란 이 호랑이들은 지금 이곳에 왜 있는가?와 같은 것들이다. 이러한 데이터 영역을 벗어난 오픈 문제들에는 너무나도 약하다.
  • not sufficiently transparent

    • dconv 등과 같은 기술이 있지만 여전히 충분히 이해할만큼 내부가 보이지 않는다.
  • not well intergrated with prior knowledge

    • 사전에 잘 정의된 지식으로 우리는 비행기도 만들고 우주산업도 발전 시켰다.
    • 하지만 딥러닝은 너무 data-driven이다. 이게 인간이 알 수 없었던 무언가를 보여주기도 하지만, 역설적으로 GAN으로 너무 밑 바닥 부터하면 너무 학습도 어렵고 나온 결과를 해석하기도 어렵다. 사전에 왜 잘 정의된 지식과 통합하지 못하는가?
    • GAN의 manipulation 하기도 쉽지 않다.
    • Physical world의 잘알려진 공식 f= ma를 만들자도 있지만, 그결과는
      • 영상으로 부터 가속도 등등을 약간 배운것이지 저 공식을 유도한 내용은 아니다.
  • not inherently distinguish causation from correlation

    • 상관 관계와 인과 관계를 구분 못한다.
      • 예: 알통이 굵으면 보수다.
  • presumes a largely stable world

    • 너무나도 불안정하다.
  • its answer often cannot be full trusted

    • 딥러닝이 낸 결과를 이해할 수 없기에 신뢰하기도 어렵다.
  • difficult to engineer with

    • 해본 사람은 알겠지만 디버깅도 어렵고 새로운 문제에 적용해서 쓸만하게 동작시키는 것도 쉽지 않다.

크게 나누면 두개

  • generalizability

    • 세상의 모든 문제를 infinite 데이터로만 학습 할 수는 없다.
  • Abstraction

    • 더 높은 지식으로 올라가지 못한다.

참고문헌


하드디스크 무료 소프트웨어 복구 방법


2012에 구매한 하디드스크를 쓰고 있다가 최근에 큰 봉변을 당했다.
DropBox GoogleDrive auto-sync를 켜둔 덕분에 간당 간당 HDD가 결국 인식불능에 도달 했다.

HDD는 2년이상이되면 급속도로 배드섹터가 많이 생기고 신뢰성이 떨어지는 장비인데 이것을 6년 가까이 사용했으니 당연한 일이다.

석박사기간 통틀어 6년간의 연구데이터가 몽땅 날라가 버리니 정말 후회가 몰려왔다. 가장 최근에 백업한 데이터가 2017.2월이니 난감한 상황이다.

어떻게든 공짜로 해결하려고 사투한 2주간의 기록을 남긴다.

  • 하드 인식이 안되면 외장 도커를 이용해서 연결
  • 절대로 포맷하지 않음
  • 문제가 생기면 Write는 무조건 중지

주의: 정말 소중한 데이터라면 전문가에게 돈을 주고 의뢰 하는게 맞습니다. 이 방법대로 했다가 데이터 날라가는것은 저는 책임지지 않습니다.

배드섹터 복구

아래 프로그램을 사용해서 복구 했다.
DRevitalize+2.42 (한글 버전)

DRevitalize+2.42.egg

연구실 후배가 설치해서 돌려준 프로그램이다.
실제 공식 홈페이지에가면 영문으로 더 상위 버전이 있긴하다.

아래와 같이 2TB 가량의 하드에 있는 배트섹터를 복구 했다.

실제론 약 3만개가 있었고 2주간 걸렸다.
배트섹터 없는 구간은 빠르지만 BAD Sector 구간에 오면 10KB이하로 속도가 급격히 떨어져서 시간이 엄청나게 오래 걸린다.

하지만 확실히 모두 고치고나면 인식이 된다.

파티션 복구

파티션까지 없어진 상황이면 이것도 복구해야 한다. 필자는 그랬다.

검색하면 도구가 많이 나온다. 하지만 대부분 Scan완료후 복구를 시도하면 10GB이상은 Pro구매 하라는 팝업창만 나온다.

정말 사람 답답한데 이런거 가지고 과금청구하는 악덕 프로그래머들에게 화가날 뿐이다.

검색 끝에 TestDisk라는 GPL라이센스를 따르는 도구를 찾았다.
역사가 오래된만큼 Windows, , uBuntu등의 다양한 OS를 지원 했다.

실제로도 상용툴에서 보여준 성능을 그대로 보여주었다.

TestDisk Dwonload
TestDisk 사용 공식 설명서

필자는 7.1 버전을 윈도우즈10 에서 사용하요 성공적으로 파티션을 복구 했다.

성공 로그
재부팅하면 파티션이 정상 복구되어 있다.

  • Partition Find and Mount
    • 무료지만 Full scan으로 검색하면 partition이 나오긴 하지만 Fragement되어서 나와서 신뢰가 안간다.


멀티 부팅 윈도우즈 설치 후 우분투 설치 (Install uBuntu alongside Windows10)


Legacy 설치 기준

  • 윈도우즈10과 Ubuntu LTS 16.04 기준
  • UEFI가 둘다 아니다.
  • legacy로 둘다 설치
  • 윈도우즈 10부터 먼저 설치한다.

BIOS 설정

  • Fast Boot를 disable
  • Secure Boot를 disable

윈도우즈 10을 Legacy BIOS에서 설치

쉽게 설치 가능

기존 파티선 축소 (윈도우즈10)

디스크 관리자에서 기존 운영체제가 설치된 파티션을 축소한다.
그래서 새 볼륨을 생성한다.

파티션 설정

우분투 설치 창에서 기타를 선택한다.
자동 옵션들은 잘 동작하지 않는다.

SWAP 파티션 생성

메모리 넉넉하면 생성안해도 된다고 한다.
그래도 일단 생성 했다.

아까 쭐인 남은공간을 선택해서 아래와 같이 설정해서 SWAP영역을 생성한다.
메모리 8GB여서 4GB 정도만 주었다.

루트 파티션 생성

이제 남은 모든 공간을 아래와 같이 루트 파티션으로 생성해 준다.

부트로더 설치경로 설정

아래와 같이 물리적 저장 장치들이 해당 이름으로 연결 되어 있다고 하면,

  • sda가 HDD
  • sdb가 SDD

기본적으로 부트로더를 설치할 장치가 보통 /dev/sda로 잡힌다.
sdb에 즉 SSD에 운영체제가 있다면 반드시 변경해서 /dev/sdb에 설치해야한다.

그렇지 않으면 부팅시에 GRUB 부트로더가 나오지 않는다.

정상적으로 설치되면 아래와 같이 GRUB으로 선택 화면이 나오게 된다.

요점은 windows boot loader가 설치된 곳에 ubuntu의 GRUB을 설치해야 멀티 부팅 화면이 제대로 나오게 된다. 추후 UEFI 모드가 활성화된 방법에서는 EFI 파티션이 보이므로 좀 더 쉽게 이 부분을 찾을 수 있다.

참고자료

UEFI 설치 기준 (Widnows10, ubuntu 둘다)

legacy 방법과 기본적으로 파티션 설정은 같다.
우분투설치시 기타선택 후에 아래의 스크린샷과 같이 GRUB이 설치될 위치를 윈도우즈 부트 매니저가 있는 EFI 파티션으로 선택해 준다.

partition

위 스샷에서 부트로더 설치할 장치는 EFI가 있는 nvme0n1p1이 된다. 이 부분을 잘 못 선택하면 EFI 경고가 뜨고 부팅이 제대로 이뤄지지 않을 수 있음을 알려 준다.

계속을 누르면 아래와 같이 설치가 진행 되고
설치

완료되어 재부팅하면 아래와 같이 나온다.
Ubuntu를 선택하면 우분투로 들어가며, Windows Boot Manager를 선택하면 윈도우즈로 접속 된다.
Grub

참고자료


몬슨 파워모니터 (Monsoon Power Monitor)를 이용한 일체형 배터리 타입의 스마트폰 전력 소모 측정


이전 포스트에서 Monsoon Power Monitor를 이용해서 전력 소모를 측정하는 법을 다뤘다.

요즘 Mobile Device는 대부분 non-removable 배터리를 채택 하고 있기 때문에 이러한 장치들에 대해서 어떻게 측정하는지를 이번 포스트에서 다룬다.

보통 논문을 보면 사진을 작게 집어 넣어서 사실 잘 안보인다.

그래서 직접 크게 사진을 찍어서 아래와 같이 첨부한다.

전극 기판 추출

일단 스마트폰을 teardown 해야 한다.
아래 사진은 nexus 5를 한것이다.
Google에 teardown 치면 왠만한 기기들은 다 있다. 보면서 공구 사서 따라하면 된다.

그 다음 battery를 추출한 다음 아래와 같이 기판을 분리한다.

다시 충전 하지 않으면 보통 배터리가 터지진 않으니 너무 겁먹지 말고 기판을 칼로 잘라내면 된다.

자르기 쉽게 고분자로 구성된 이음 부분이 있다. 그 부만 제거하면 쉽게 분리된다.

몬슨과 연결

이제 해당 기판을 다시 스마트폰에 연결하고
양극 음극에 맞춰서 몬슨 VCC Ground를 해준다.

그럼 정상적으로 스마트폰이 켜지고 전력 소모가 측정된다.

어떤 스마트폰이나 스마트시계와 같은 웨러블 장치는 기판에 보호 회로가 있어서 무한대 저항이 걸려서 cutoff 될 수도 있다. 그럴 때는 납땜을 해서 연결해야 한다.

사실 이러한 저전력 연구는 제조사에서 하면 별것도 아닌데 이런 off the shelf 제품을 가지고 직접 하려고하면 완제품이다보니 이래저래 잔손이 많이 간다.


학습 모델의 재사용 (Transfer Learning)


DNN 모델을 스케치 단계서 부터 전부 다시 학습하는 것은 어려운 일이다. 정말 많은 데이터를 필요로 하기 때문에 학습이 거의 대부분 실패 한다.

여러 방법이 있을 수 있지만 여기서는 Transfer Learning을 다룬다. 잘알려진pretrainted model을 이용해서 마지막 final layer만 수정해서 본인의 목적에 맞게 다시 학습 시키는 방법이다.
혹자는 Fine tuning이라 부르기도 한다.

본인 보유한 데이터 양에 따라 어느 정도 레이어들을 수정할지가 결정된다. 보통 앞부분 layer는 추상적인 feature를 extraction하는 레이어이므로 건들지 않고 마지막 레이러들을 수정한다.

하지만 pretrained model이 현재 수행하려고 하는 작업에 전혀 관련이 없다면Transfer Learning이 의미가 퇴색 된다.
이 경우 unsupervised pre-training방법으로 RBM 또는 Autoencoder방법을 사용하거나 실제로 labeling 데이터를 왕창 모아서 학습을 해야한다.
하지만 scarth부터 random initialization 상태에서 학습하는 것은 매우 도전적인 일이므로 추천 하진 않는다.

결국 아무 때나 Deep Learning을 쓰기엔 무리가 있다.

개념설명

아래와 같이 ImageNet모델이 있다고 가정한다. 1000개를 classify할 수 있다.

이것을 가지고 고양이 Tigger, Misty, Neither 세개를 구분하는 모델을 만든다. 당연히 이런 특정 고양이 이미지는 많이 없다. 따라서 pre-trained 모델을 사용 한다.

이 때 학습할 때 마지막 softmax layer만 학습하게 된다.
이전 데이터는 freeze하게 된다. 이것은 deep learning framework에 따라 구현코드가 달라진다. 이후에는 TensorFlow를 이용해서 이 방법을 다룬다.

  • trainablePrameter =0, freeze = 1

그리고 이러한 freeze레이어의 숫자는 training data의 양에 따라서 다르게 적용할 수 있다.

Tensorflow 구현 (개념 설명)

전부다 저장하고 restore하는 코드

[...] # construct the original model

with tf.Session() as sess:
	saver.restore(sess, "./my_original_model.ckpt")
	[...] # Train it on your new task

부분적으로 복구하는 방법

init = tf.global_variables_initializer()
reuse_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope="hidden[123]")
reuse_vars_dict = dict([(var.name, var.name) for var in reuse_vars])
original_saver = tf.Saver(reuse_vars_dict) # saver to restore the original model

new_saver = tf.Saver() # saver to save the new model

with tf.Session() as sess:
	sess.run(init)
	original_saver.restore("./my_original_model.ckpt") # restore layers 1 to 3
	[...] # train the new model
	new_saver.save("./my_new_model.ckpt") # save the whole model

좀 더 상세한 내용은 이전 포스트를 참조한다.

실제코드

MNIST 데이터를 가지고 fully connected DNN의 pre-trained model을 로드해서 fine-tuning 하는 방법을 다룬다.
전체 구현코드는 Github Link에 있다.

  • 모델 학습
  • 전체 로드
  • 일부분 로드
  • 앞부분 레이어 고정후 뒷 부분만 학습
  • 고정레이어 cache후 뒷 부분만 학습 (트레이닝 속도 향상)

기본 라이브러리 로딩

import tensorflow as tf
# Common imports
import numpy as np
import os
from tensorflow.examples.tutorials.mnist import input_data

def reset_graph(seed=42):
    tf.reset_default_graph()
    tf.set_random_seed(seed)
    np.random.seed(seed)
def leaky_relu(z, name=None):
    return tf.maximum(0.01 * z, z, name=name)

mnist = input_data.read_data_sets("./")
Extracting ./train-images-idx3-ubyte.gz
Extracting ./train-labels-idx1-ubyte.gz
Extracting ./t10k-images-idx3-ubyte.gz
Extracting ./t10k-labels-idx1-ubyte.gz

모델 생성

하이퍼파라메터 설정

reset_graph()

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 50
n_hidden3 = 50
n_hidden4 = 50
n_hidden5 = 50
n_outputs = 10

n_epochs = 20
batch_size = 50

모델 생성

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2")
    hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name="hidden3")
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="hidden4")
    hidden5 = tf.layers.dense(hidden4, n_hidden5, activation=tf.nn.relu, name="hidden5")
    logits = tf.layers.dense(hidden5, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

learning_rate = 0.01
threshold = 1.0

optimizer = tf.train.GradientDescentOptimizer(learning_rate)
grads_and_vars = optimizer.compute_gradients(loss)
capped_gvs = [(tf.clip_by_value(grad, -threshold, threshold), var)
              for grad, var in grads_and_vars]
training_op = optimizer.apply_gradients(capped_gvs)

init = tf.global_variables_initializer()
saver = tf.train.Saver()

학습

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        if epoch % 5 == 0:
            acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
            acc_test = accuracy.eval(feed_dict={X: mnist.validation.images, y: mnist.validation.labels})
            print(epoch, "Batch accuracy:", acc_train, "Validation accuracy:", acc_test)

    save_path = saver.save(sess, "./my_model_final.ckpt")
0 Batch accuracy: 0.94 Validation accuracy: 0.9006
5 Batch accuracy: 1.0 Validation accuracy: 0.9642
10 Batch accuracy: 0.96 Validation accuracy: 0.9712
15 Batch accuracy: 0.98 Validation accuracy: 0.9772
20 Batch accuracy: 1.0 Validation accuracy: 0.9774
25 Batch accuracy: 1.0 Validation accuracy: 0.9786
30 Batch accuracy: 1.0 Validation accuracy: 0.9778
35 Batch accuracy: 1.0 Validation accuracy: 0.9776

모델 로딩후 재학습

여기서 부터 모델을 불러와서 다시 fine-tuning 하는 부분을 구현 한다.

reset_graph()
saver = tf.train.import_meta_graph("./my_model_final.ckpt.meta")

로딩가능한 operation이 무엇인지 확인한다.

for op in tf.get_default_graph().get_operations():
    print(op.name)
X
y
hidden1/kernel/Initializer/random_uniform/shape
hidden1/kernel/Initializer/random_uniform/min
hidden1/kernel/Initializer/random_uniform/max
[생략]
[생략]
save/RestoreV2_11/tensor_names
save/RestoreV2_11/shape_and_slices
save/RestoreV2_11
save/Assign_11
save/restore_all

파라메터가 너무 많기 때문에 TensorBoard로 확인한다.
그다음 아래와 같이 get_tensor_by_name get_operation_by_name으로 로드한다.

X = tf.get_default_graph().get_tensor_by_name("X:0")
y = tf.get_default_graph().get_tensor_by_name("y:0")

accuracy = tf.get_default_graph().get_tensor_by_name("eval/accuracy:0")

training_op = tf.get_default_graph().get_operation_by_name("GradientDescent")

다른 사람의 편의를 위해서 import operation collection으로 미리 정의해 줄수도 있다.

for op in (X, y, accuracy, training_op):
    tf.add_to_collection("my_important_ops", op)

이렇게 하면 다른 사람들이 쉽게 로딩 가능하다.

X, y, accuracy, training_op = tf.get_collection("my_important_ops")

아래와 같이 이제 본인의 데이터로 본격적으로 학습이 가능하다.

with tf.Session() as sess:
    saver.restore(sess, "./my_model_final.ckpt")
    # continue training the model...
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt

아니면 모델을 로드에서 Testing만 가능하다.

with tf.Session() as sess:
    saver.restore(sess, "./my_model_final.ckpt")

    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")    
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
0 Test accuracy: 0.9743
1 Test accuracy: 0.9744
2 Test accuracy: 0.9756
3 Test accuracy: 0.9743
4 Test accuracy: 0.9751
5 Test accuracy: 0.975
6 Test accuracy: 0.9741
7 Test accuracy: 0.9742
8 Test accuracy: 0.9751
9 Test accuracy: 0.9748
10 Test accuracy: 0.9744
11 Test accuracy: 0.9747
12 Test accuracy: 0.9746
13 Test accuracy: 0.9747
14 Test accuracy: 0.9746
15 Test accuracy: 0.9747
16 Test accuracy: 0.9742
17 Test accuracy: 0.9749
18 Test accuracy: 0.9746
19 Test accuracy: 0.9748
20 Test accuracy: 0.975
21 Test accuracy: 0.9746
22 Test accuracy: 0.9745
23 Test accuracy: 0.975
24 Test accuracy: 0.9744
25 Test accuracy: 0.9743
26 Test accuracy: 0.9743
27 Test accuracy: 0.9745
28 Test accuracy: 0.9746
29 Test accuracy: 0.9749
30 Test accuracy: 0.9746
31 Test accuracy: 0.9747
32 Test accuracy: 0.9747
33 Test accuracy: 0.9743
34 Test accuracy: 0.9746
35 Test accuracy: 0.9746
36 Test accuracy: 0.9749
37 Test accuracy: 0.9751
38 Test accuracy: 0.9748
39 Test accuracy: 0.9743

다른 방법은 그냥 원래 모델에 대한 code가 있다면 그것을 로딩해도 된다. 그렇게 하면import_meta_graph()를 호출하지 않아도 된다.

마지막 4번째 레어어만 수정해서 재학습하기 (not freezing the lower layers)

  • import_meta_graph()로 전체 graph를 모두 불러온다음 4번째 Layer를 무시한다.
  • 즉 3번째 레이어 까지만 재사용한다.
  • 그리고 output 레이어도 재설정한다.
  • 그리고 이것으로 optimizer를 이용해서 최적화한다.
  • 이렇게 생성된 결과를 새로운 파일에 저장한다.
reset_graph()

n_hidden4 = 20  # new layer
n_outputs = 10  # new layer

saver = tf.train.import_meta_graph("./my_model_final.ckpt.meta")

X = tf.get_default_graph().get_tensor_by_name("X:0")
y = tf.get_default_graph().get_tensor_by_name("y:0")

hidden3 = tf.get_default_graph().get_tensor_by_name("dnn/hidden4/Relu:0")

new_hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="new_hidden4")
new_logits = tf.layers.dense(new_hidden4, n_outputs, name="new_outputs")

with tf.name_scope("new_loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=new_logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("new_eval"):
    correct = tf.nn.in_top_k(new_logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("new_train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)

init = tf.global_variables_initializer()
new_saver = tf.train.Saver()

새로운 레이어를 정의하고 loss namesapce를 다르게 정의 했기 때문에saver.restore()후에 값이 중복 되지 않는다.

with tf.Session() as sess:
    init.run()
    saver.restore(sess, "./my_model_final.ckpt")

    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = new_saver.save(sess, "./my_new_model_final.ckpt")
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
0 Test accuracy: 0.9665
...
...
39 Test accuracy: 0.9751

마지막 4번째 레어어만 수정해서 재학습하기 (freezing the lower layers)

구현을 위해서는 2가지 방법이 존재한다.

  • tf.GraphKeys.TRAINABLE_VARIABLES, scope="outputs")을 이용한 방법
  • tf.stop_gradient를 이용한방법

tf.GraphKeys.TRAINABLE_VARIABLES 이용

reset_graph()

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300 # reused
n_hidden2 = 50  # reused
n_hidden3 = 50  # reused
n_hidden4 = 20  # new!
n_outputs = 10  # new!

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")       # reused
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2") # reused
    hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name="hidden3") # reused
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="hidden4") # new!
    logits = tf.layers.dense(hidden4, n_outputs, name="outputs")                         # new!

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

학습할 대상을 정규식에 의해서 scope을 정해서 불러온다.
결과는 아래와 같다.

tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,
                                   scope="outputs")
[<tf.Variable 'outputs/kernel:0' shape=(20, 10) dtype=float32_ref>,
<tf.Variable 'outputs/bias:0' shape=(10,) dtype=float32_ref>]
with tf.name_scope("train"):                                         # not shown in the book
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)     # not shown
    train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,
                                   scope="outputs")
    training_op = optimizer.minimize(loss, var_list=train_vars)
init = tf.global_variables_initializer()
new_saver = tf.train.Saver()

따로 새롭게 namesapce를 정의하지 않고 load할 때 정규식을 이용해서 일부분만 불러온다.
이렇게 하면 name을 변경할 필요 없다.

tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]|outputs") # regular expression
[<tf.Variable 'hidden1/kernel:0' shape=(784, 300) dtype=float32_ref>,
 <tf.Variable 'hidden1/bias:0' shape=(300,) dtype=float32_ref>,
 <tf.Variable 'hidden2/kernel:0' shape=(300, 50) dtype=float32_ref>,
 <tf.Variable 'hidden2/bias:0' shape=(50,) dtype=float32_ref>,
 <tf.Variable 'hidden3/kernel:0' shape=(50, 50) dtype=float32_ref>,
 <tf.Variable 'hidden3/bias:0' shape=(50,) dtype=float32_ref>,
 <tf.Variable 'outputs/kernel:0' shape=(20, 10) dtype=float32_ref>,
 <tf.Variable 'outputs/bias:0' shape=(10,) dtype=float32_ref>]
reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]|outputs]") # regular expression
reuse_vars_dict = dict([(var.op.name, var) for var in reuse_vars])
restore_saver = tf.train.Saver(reuse_vars_dict) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")

    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
0 Test accuracy: 0.9221
...
...
39 Test accuracy: 0.9556

tf.stop_gradient 이용

reset_graph()

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300 # reused
n_hidden2 = 50  # reused
n_hidden3 = 50  # reused
n_hidden4 = 20  # new!
n_outputs = 10  # new!

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")
with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                              name="hidden1") # reused frozen
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,
                              name="hidden2") # reused frozen
    hidden2_stop = tf.stop_gradient(hidden2)
    hidden3 = tf.layers.dense(hidden2_stop, n_hidden3, activation=tf.nn.relu,
                              name="hidden3") # reused, not frozen
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu,
                              name="hidden4") # new!
    logits = tf.layers.dense(hidden4, n_outputs, name="outputs") # new!
with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)

위와 같이 hidden2_stop = tf.stop_gradient(hidden2)을 사용해서 중간 레이어를 만든다.
그 이후에는 트레이닝 코드는 위 방식과 정확히 일치한다.

%%time
reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]") # regular expression
reuse_vars_dict = dict([(var.op.name, var) for var in reuse_vars])
restore_saver = tf.train.Saver(reuse_vars_dict) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")

    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_new_model_final.ckpt")
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
0 Test accuracy: 0.9654
...
...
19 Test accuracy: 0.9738
CPU times: user 23.1 s, sys: 852 ms, total: 23.9 s
Wall time: 13.5 s

Fronzen Layer를 cache해서 학습속도를 올리는 방법

Frozen 레이어는 변화하지 않기 때문에 이것은 cache해서 재사용 할 수 있다.

reset_graph()

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300 # reused
n_hidden2 = 50  # reused
n_hidden3 = 50  # reused
n_hidden4 = 20  # new!
n_outputs = 10  # new!

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu,
                              name="hidden1") # reused frozen
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu,
                              name="hidden2") # reused frozen & cached
    hidden2_stop = tf.stop_gradient(hidden2)
    hidden3 = tf.layers.dense(hidden2_stop, n_hidden3, activation=tf.nn.relu,
                              name="hidden3") # reused, not frozen
    hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu,
                              name="hidden4") # new!
    logits = tf.layers.dense(hidden4, n_outputs, name="outputs") # new!

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("train"):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate)
    training_op = optimizer.minimize(loss)
reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES,
                               scope="hidden[123]") # regular expression
reuse_vars_dict = dict([(var.op.name, var) for var in reuse_vars])
restore_saver = tf.train.Saver(reuse_vars_dict) # to restore layers 1-3

init = tf.global_variables_initializer()
saver = tf.train.Saver()

핵심 방법은 1번만 트레이닝해서 h2_cache를 만든다음 이것을 suffling한 index를 가지고 epoch을 돌면서 training하는 것이다.
메모리가 충분하다면 이러한 방법이 가능하다.
뒷 부분을 계산하지 않기 때문에 training속도를 증가 시킬 수 있다.13.5초에서9.63초로 속도가 빨라진 것을 알 수 있다.

%%time
import numpy as np

n_batches = mnist.train.num_examples // batch_size

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_model_final.ckpt")
    
    h2_cache = sess.run(hidden2, feed_dict={X: mnist.train.images})
    h2_cache_test = sess.run(hidden2, feed_dict={X: mnist.test.images}) # not shown in the book

    for epoch in range(n_epochs):
        shuffled_idx = np.random.permutation(mnist.train.num_examples)
        hidden2_batches = np.array_split(h2_cache[shuffled_idx], n_batches)
        y_batches = np.array_split(mnist.train.labels[shuffled_idx], n_batches)
        for hidden2_batch, y_batch in zip(hidden2_batches, y_batches):
            sess.run(training_op, feed_dict={hidden2:hidden2_batch, y:y_batch})

        accuracy_val = accuracy.eval(feed_dict={hidden2: h2_cache_test, # not shown
                                                y: mnist.test.labels})  # not shown
        print(epoch, "Test accuracy:", accuracy_val)                    # not shown

    save_path = saver.save(sess, "./my_new_model_final.ckpt")
INFO:tensorflow:Restoring parameters from ./my_model_final.ckpt
0 Test accuracy: 0.9648
...
...
19 Test accuracy: 0.9736
CPU times: user 20.7 s, sys: 428 ms, total: 21.1 s
Wall time: 9.63 s

참고문헌

Coursera deep learning ai, Andrew Ng.
Hands-On Machine Learning with Scikit-Learn and Tensorflow, Aureien Geron


TensorFlow 모델을 저장하고 불러오기 (save and restore)


해당 튜토리얼에 사용한 코드는 개인 GitHub Link에서 확인 할 수 있다.

저장 복구를 위해서는 두개의 파일이 필요하다.

a) Meta graph

Tensorflow graph를 저장 하게 된다. 즉 all variables, operations, collections 등을 저장 한다. .meta로 확장자를 가진다.

b) Checkpoint file

binary 파일로 weights, biases, gradients 등을 저장 한다.
0.11부터는 두개의 파일로 저장된다.

  • model.ckpt.data-00000-of-00001
  • model.ckpt.index

.data파일의 경우 training variable를 가지고 있다.
여전히 checkpoint파일도 보유하고 있지만 이것은 단순히 최근 상태만을 기록하고 있다.

모델 저장 방법

saver = tf.train.Saver()를 통해서 가능 하다.

import tensorflow as tf
w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1')
w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2')
saver = tf.train.Saver()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess, 'my_test_model')

# This will save following files in Tensorflow v >= 0.11
# my_test_model.data-00000-of-00001
# my_test_model.index
# my_test_model.meta
# checkpoint

만약 1000 interations 이후에 model을 저장하고 싶다면 아래와 같이 한다.

saver.save(sess, "my_test_model", global_step=1000)

이렇게 하면 아래처럼 모델뒤에 -1000이라는 숫자가 이름에 붙어서 나오게 됩니다.

my_test_model-1000.index
my_test_model-1000.meta
my_test_model-1000.data-00000-of-00001
checkpoint

모델의 구조는 같기 때문에 .meta파일의 경우 1000 interation당 매번 생성할 필요는 없다.

모델 값만 저장하고 graph는 저장하지 않는 코드는 아래와 같다.

saver.save(sess, 'my-model', global_step=step,write_meta_graph=False)

만약 최근 2시간동안 4개의 모델만 저장하고 싶다면 아래와 같이 옵션을 설정한다.

#saves a model every 2 hours and maximum 4 latest models are saved.
saver = tf.train.Saver(max_to_keep=4, keep_checkpoint_every_n_hours=2)

망약 tf.train.Saver()에 아무것도 지정하지 않았다면 모든 변수들을 저장하게 된다.
특정 variables/collections을 저장하고 싶다면 그것을 지정하면 된다.
List, dictionary자료구조도 받으니 그것을 잘 활용 한다.

import tensorflow as tf
w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1')
w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2')
saver = tf.train.Saver([w1,w2])
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess, 'my_test_model',global_step=1000)

모델 읽기

두 가지 일을 해야한다.

a) 네트워크 생성

.meta파일을 생성 했으므로 이것을 불러오는 방식으로 network을 재생성 할 수 있다.
.meta파일을 불러오기 위해서는 tf.train.import() 함수를 이용한다.

saver = tf.train.import_meta_graph('my_test_model-1000.meta')

이렇게 하면 현재 그래프에 이어 붙는 형식으로 동작하므로tf.reset_default_graph()를 실행해서 default graph로 초기화 해주는 것이 안전하다.

b) 파라메터 로딩

tf.train.Saver()를 이용해서 파라메터를 로딩한다.

with tf.Session() as sess:
  new_saver = tf.train.import_meta_graph('my_test_model-1000.meta')
  new_saver.restore(sess, tf.train.latest_checkpoint('./'))
with tf.Session() as sess:    
    saver = tf.train.import_meta_graph('my-model-1000.meta')
    saver.restore(sess, tf.train.latest_checkpoint('./'))
    print(sess.run('w1:0'))
##Model has been restored. Above statement will print the saved value of w1.

저장된 모델로 실제 작업하기

이제 위해서 다룬 내용을 토대로 종합적으로 간단한 neural net.을 생성하고 이것을 저장한다음 다시 불러오는 코드를 작성해 본다.
이런한 작업은 추후에 transfer learning이나 testing만 별도로 작업하기 위해서 사용 될 수 있다.

아래의 코드는 $y=(w1+w2) \times b$ 를 구현한 내용이다.
여기서 핵심은 추후에 variable operation을 각각 불러오기 위해서 name을 반드시 주어야 한다.
나중에 본인 model을 공유할 때도 이름을 잘 정해주어야 다른 사람이 가져다가 본인들 목적에 맞춰서 fine-tuning해서 사용할 수 있다.

모델 생성 및 저장

import tensorflow as tf

# Prepare to feed input, i.e. feed_dict and placeholders
w1 = tf.placeholder(tf.float32, name="w1")
w2 = tf.placeholder(tf.float32, name="w2")
b1 = tf.Variable(2.0,dtype=tf.float32, name="bias")
feed_dict = {'w1': 4.0, 'w2': 8.0}

# Define a test operation that we will restore
w3 = w1 + w2
w4 = tf.multiply(w3, b1, name="op_to_restore")
sess = tf.Session()
sess.run(tf.global_variables_initializer())

# Create a saver object which will save all the variables
saver = tf.train.Saver()

# Run the operation by feeding input
result = sess.run(w4, {w1:feed_dict['w1'], w2:feed_dict['w2']})
print(result)
# Prints 24 which is sum of (w1+w2)*b1

# Now, save the graph
saver.save(sess, './my_test_model', global_step=1000)

실행결과는 24이다.

모델 불러오기와서 새로운 입력값으로 처리

모델을 복구하고 feed_dict을 다르게 입력하는 코드이다.

import tensorflow as tf

sess=tf.Session()    
#First let's load meta graph and restore weights
saver = tf.train.import_meta_graph('my_test_model-1000.meta')
saver.restore(sess,tf.train.latest_checkpoint('./'))


# Now, let's access and create placeholders variables and
# create feed-dict to feed new data

graph = tf.get_default_graph()
w1 = graph.get_tensor_by_name("w1:0")
w2 = graph.get_tensor_by_name("w2:0")
feed_dict ={w1:13.0,w2:17.0}

#Now, access the op that you want to run. 
op_to_restore = graph.get_tensor_by_name("op_to_restore:0")

print (sess.run(op_to_restore,feed_dict))
#This will print 60 which is calculated 
#using new values of w1 and w2 and saved value of b1. 

실행결과는 60이다.

위 예제는 Tensor를 이용해서 간단하게 구현하다보니 모두get_tensor_by_name()으로 가능하지만 실제로 operation과 placeholder는 각각 다르게 load해야 한다.

  • placeholder 불러오기

    • graph.get_tensor_by_name()
  • operation 불러오기

    • graph.get_operation_by_name()

로딩 가능한 현재 graph에서의 operation의 종류이다.

for op in tf.get_default_graph().get_operations():
    print(op.name)

모델을 불러오고 operation과 layer 추가

import tensorflow as tf

sess=tf.Session()    
#First let's load meta graph and restore weights
saver = tf.train.import_meta_graph('my_test_model-1000.meta')
saver.restore(sess,tf.train.latest_checkpoint('./'))


# Now, let's access and create placeholders variables and
# create feed-dict to feed new data

graph = tf.get_default_graph()
w1 = graph.get_tensor_by_name("w1:0")
w2 = graph.get_tensor_by_name("w2:0")
feed_dict ={w1:13.0,w2:17.0}

#Now, access the op that you want to run. 
op_to_restore = graph.get_tensor_by_name("op_to_restore:0")

#Add more to the current graph
add_on_op = tf.multiply(op_to_restore,2)

print (sess.run(add_on_op,feed_dict))
#This will print 120.

실행결과 120이다.

참고자료

http://cv-tricks.com/tensorflow-tutorial/save-restore-tensorflow-models-quick-complete-tutorial/


Batch 크기의 결정 방법


보통 vectorization방법으로 gradient descent알고리즘의 효율을 높이게 된다.

하지만 input 데이터가 너무 크다면 그 방법을 사용할 수 없다.
메모리 문제도 발생하고 한 번 interation (epoch)당 시간이 너무 오래 걸리기 때문이다.

Batch-gradient descent mini-bach gradient descent의 cost 그래프의 차이는 아래와 같다.

Choosing your mini-batch size

  • Mini-batch 크기가 전체 트레이닝 셋 데이터 사이즈인 m과 같다면 이것은 Batch gradient descent방법이다.

    • (+) 상대적으로 아래의 contour그래프를 보면 알 수 있듯이 적은 noise Large step으로 글로벌 미니멈에 수렴한다.
    • (+) vectorization 효율이 좋다.
    • (-) interation당 속도가 느리다.
    • (-) 메모리 여유 공간에 따라서 실행 불가능 할 수도 있다.
  • Mini-batch 크기가 1이라면,Stochastic gradient descent라고 부른다.

    • (+) 적은 메모리로 동작 가능하다. noise한 부분은 learning rate을 작게하면 완화 할 수 있다.
    • (-) vectorization 효율이 없다. training data를 1개만 사용하기 때문이다.
  • Mini-batch크기를 너무 작게도 크게도 하지 않는다.

    • (+) vectorization을 효과 본다.
    • (+) interation당 긴 시간없이 progress를 만들 수 있다.

최종적 가이드라인

  • 데이터가 별로 없다면 batch gradient descent를 쓴다.
    • ex 2,000 정도
  • mini-batch를 선택
    • 64, 128, 256, 512 사이즈로 선택한다. 메모리 사이즈에 맞춰서
  • CPU/GPU memory 사이즈에 맞춰서 조절 한다.

Take-away message
SGD GD가 같은 글로벌 cost 최소점에 도달 할 수 있다는 것은 증명이 되어 있지만, neural netwrok은 convex가 아니기 때문에 batch 크기의 설정 방법에 따라 수렴하는 조건이 다를 수 있다.

Batch size는 일반적으로 메모리가 감당할 수 있는 정도까지 최대한 크게 잡는게 좋을것 같다.

참고자료

Coursera DeepLearning ai, Andrew Ng


딥러닝 입력 데이터 정규화 (Normalizing inputs)


학습을 수행하기 전에 값의 범위를 normalization 하는 것은 중요하다. 그 이유는 아래와 같다.

입력 변수가 MLP에서와 같이 선형 적으로 결합된다면 적어도 이론 상으로는 입력을 표준화하는 것이 거의 필요하지 않습니다.
그 이유는 해당 weight bais를 변경하여 입력 벡터를 재조정하면 이전과 완전히 똑같은 결과를 남길 수 있기 때문입니다.
그러나 입력을 Standardization하면 학습을 더 빨리하고 지역 최적의 상태에 빠지게 될 가능성을 줄이는 다양한 실용적인 이유가 있습니다.
또한, 표준화 된 입력을 통해 Gradient Descent  Bayesian estimation을 보다 편리하게 수행 할 수 있습니다.

Normalization

수식 : (요소값 - 최소값) / (최대값 - 최소값)
설명 : 전체 구간을 0~1사이의 값으로 맞춰 준다.

Standardization

수식 : (요소값 - 평균) / 표준편차
설명 : 평균은 0 표준편차는

$$\mu = \frac{1}{m} \sum_{i=1}^{m}{x^{(i)}}$$
$$x := x -\mu$$
$$\sigma^{2}=\frac{1}{m}\sum_{i=1}^{m}{(x^{(i)}-\mu)^{2}}$$

직관적 이해

아래와 같이 Unnormalized된 상태에서는 Learning Rate을 매우 작게 설정해야 정상적을 학습이 된다.
이유는 cost 그래프가 elongated하기 때문이다. 따라서 elongated contour의 모습을 가진다.
아래와 같이 Input의 Range가 서로 다르다면 Gradient Descent Algorithm을 적용하는것이 매우 까다로워지는 상황이 발생 한다.

하지만 normalization을 적용하면 좀 더 spherical contour를 가지게 된다.
이렇게 하면 좀 더 Gradient Descent Algorithm으로 쉽게 그리고 빠르게 최적화 지점을 찾게 된다.

구현 코드

아래와 같은 코드를 가질 때
x가 1.0
y가 0.0
이렇게 1.0~0 사이의 값으로 입력과 출력이 정규화가 잘되면 학습이 잘 이뤄 진다.
Learning rate을 적절히 값을 잡아도 학습이 잘된다.

import tensorflow as tf
x = tf.constant(1.0, name='input')
w = tf.Variable(0.8, name='weight')
y = tf.mul(w, x, name='output')
y_ = tf.constant(0.0, name='correct_value')
loss = tf.pow(y - y_, 2, name='loss')
train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)

for value in [x, w, y, y_, loss]:
    tf.scalar_summary(value.op.name, value)

summaries = tf.merge_all_summaries()

sess = tf.Session()
summary_writer = tf.train.SummaryWriter('log_simple_stats', sess.graph)

sess.run(tf.initialize_all_variables())
for i in range(100):
    if i % 10 ==0:
        print("epoch {}, output: {}".format(i, sess.run(y)))
    summary_writer.add_summary(sess.run(summaries), i)
    sess.run(train_step)
epoch 80, output: 0.01321229338645935
epoch 90, output: 0.007910688407719135

하지만 x의 범위르 10배만 넓혀서 10으로 하면 학습이 실패 한다.
이 때는 Learning rate을 더 작게 주어야 한다.

import tensorflow as tf
x = tf.constant(10.0, name='input')
w = tf.Variable(0.8, name='weight')
y = tf.mul(w, x, name='output')
y_ = tf.constant(0.0, name='correct_value')
loss = tf.pow(y - y_, 2, name='loss')
train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)

for value in [x, w, y, y_, loss]:
    tf.scalar_summary(value.op.name, value)

summaries = tf.merge_all_summaries()

sess = tf.Session()
summary_writer = tf.train.SummaryWriter('log_simple_stats', sess.graph)

sess.run(tf.initialize_all_variables())
for i in range(100):
    if i % 10 ==0:
        print("epoch {}, output: {}".format(i, sess.run(y)))
    summary_writer.add_summary(sess.run(summaries), i)
    sess.run(train_step)
epoch 70, output: nan

통상의 Learning Rate의 문제와 비슷하게 발생 한다.

참고 사이트

http://stackoverflow.com/questions/4674623/why-do-we-have-to-normalize-the-input-for-an-artificial-neural-network


TensorFlow Lite 예제 실행


구글이 공개한 TensorFlow Lite의 샘플 예제를 실행하는 방법을 다룬다.

아래는 실행 예제를 Galaxy S8에서 실행한 모습이다. 

첫 번째 영상은 TF lite를 이용해서 Quantized MobileNet 딥러닝 모델을 실행한 것이다. 두 번째 영상은 TF Mobile을 이용해서

Inception v5 딥러닝 모델을 실행한 영상이다.

     


카메라로 들어온 영상을 실시간으로 처리하게 된다. 홈페이지에서는 Quantized mobilenet이 좀더 경량화 되었기 때문에 Inceptionv5보다 정확도 손실이 있다고 했지만 별 차이는 모르겠다. 

샘플 예제를 실행하는 방법은 아래 3가지가 있다.

  • prebuilt binary 다운로드
  • Android 3.0을 이용해서 App 컴파일
  • TF lite 코드와 demo app을 다운 받아서 bazel로 빌드한다.

pre-built binary 다운로드 방법

TfLiteCameraDemo.apk 이것을 다운 받아서 실행 한다.

무슨 문제가 있는지 galaxy s8에서 설치가 안된다.

Building in Android Studio using TensorFlow Lite AAR from JCenter

가장 컴파일 하기 쉬운 방법이다.

조건 사항은 아래와 같다.

  • Android Studio 3.0 이상

  • Android SDK 버전 26이상

  • Androi NDK 버전 14 이상

  • Tensorflow코드를 git clone https://github.com/tensorflow/tensorflow으로 다운 받은 다음,tensorflow/contrib/lite/java/demo 디렉터리를 Android Studio로 Import 한다.

  • Quantized Mobilenet을 다운로드 받는다. mobilenet-quant_v1_224.tflite이다. 이것을 그리고 import한 앱에서 assets에 압축을 풀어서 넣는다.

    • wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v1_224_android_quant_2017_11_08.zip
  • Android Studio에서 demo app을 run 한다.

소스코드로 빌드하는 방법

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite#downloading-the-pre-built-binary

참고 블로그

https://soundlly.github.io/2017/11/20/tensorflowlite-moblienet-demo/


TensorFlow Lite 개발자 프리뷰 공개


2017년 11월 14일 드디어 TensorFlow Lite 개발자 프리뷰가 공개 되었다. Google Developers post를 요약해 본다.

Tensorflow Lite는 low latency inference를 on-device에서 지원 한다.

특성은 아래와 같다.

  • Lightweight: small binary size, fast initialization/startup
  • Cross-platform: Android and iOS
  • Fast: Optimized for mobile devices, including dramatically improved

재미 있는 것은 TF lite는 purpose-built custom hardware를 지원 한다는 것이다. 이를 위해서 Android Neural networks API를 포함한다.
각 하드웨어별 가속기를 사용하는 API 집단이다.

Architecture

위 전체 구조에서 각각의 컴포넌트는 다음과 같다.

  • TensorFlow Model: 디스크에 있는 TF 학습 모델
  • TensorFlow Lite Convert: model을 TF Lite file format으로 변경하는것
  • TensorFlow Lite Model File: FlatBuffers 타입에서의 최적화된 모델

그다음 해당 TensorFlow Lite Model file은 모바일앱에 탑제 된다.

이 때 앱은 다음과 같이 구성된다.

  • Java API: C++을 감싼 wrapper API
  • C++ API: 모델을 읽고 interpreter를 호출하는 기능
  • Interpreter: TF Mobile이 normal set of operators가 1.5Mb인것에 비해서 이것은 operators 포함해서 300KB 수준이다.

그리고 선택된 장치에서 interpreter가 알아서 Android Neural Networks API를 통해서 hardware acceleration을 사용하게 된다. 지원 되는게 없다면 CPU를 사용한다.

추가적으로 개발자가 custom kernel을 개발할 수도 있다. 그리고 이것을 interpreter에서 사용 가능 하다.

Models

이미 많은 최적화된 학습 모델들을 지원한다.

  • MobileNet: 1000개의 object 클래스들은 디텍션하는 모델을 모바일과 임베디드에 맞춰서 다시 진행
  • Inception v3: 약간 큰 사이즈로 더 높은 정확도를 제공한다. 기술은 MobileNet과 유사하다.
  • Smart Reply: one-touch 응답을 위한 on-device 대화 모델이다. Android Wear상에서 앱개발할 때 사용 할 수 있다.

Inception V3나 MobileNet은 ImageNet 데이터셋으로 학습되어 있으므로 본인 목적에 맞게 사용하려면 Transfer learning을 해서 모델을 재학습 하면된다.

TensorFlow Mobile과 TensorFlow Lite와의 차이점

이미 TF Mobile이 있었다. 그것과의 차이점은 무엇인가

  • Lite 버전은 Mobile 버전은 혁신판이다. 좀 더 작은 binary 사이즈와 의존성으로도 더 좋은 성능을 보장 한다.
  • TF Lite가 모든걸 커버하진 않는다. 따라서 어떠한 production case에서는 TF Mobile을 사용해서 직접 코딩 해야할 수도 있다.
  • TF Lite는 제한적인 operator들을 지원 한다. 따라서 모든 알려진 model들이 동작하긴 어렵다. TF Mobile은 모든 operator들을 지원 하므로 다 구현 할 수 있다.

앞으로 계속 향상될 것이지만, TF Lite는 잘 알려진 모델을 하드웨어 가속해서 동작하는데 초점을 맞춘다. 개발 복잡도를 낮추기 위해서이다.
더 자세한 내용은 TF Lite documentation pages를 참고한다.

출처

[원글]


'AI > Embedded Deep learning' 카테고리의 다른 글

ONNX  (4) 2020.05.04
Glow 설치법  (1) 2019.11.19
Coral Dev Board (Google Edge TPU) 설정 및 사용후기  (2) 2019.08.13
Glow: graph lowering compiler for hardware accelerators  (0) 2019.02.07
TensorFlow Lite 예제 실행  (2) 2017.12.05

ShareLaTex 설치 및 사용법


협업형 online real time 에디터이다.
http://www.sharelatex.com/ 에서 무료로 사용이 가능하다.
하지만 여러사람을 invite하거나 속도가 마음에 안든다면, 각 연구실에 개인이 구축할 수도 있다.
Open-Source이기 때문이다.

Github 주소: https://github.com/sharelatex/sharelatex

Docker 기반으로 image가 잘 만들어져 있어서 설치법은 간단하다.

필자는 uBuntu 16.04 LTS 버전에서 수행 했다.

shareLatex 설치 및 실행

sudo apt-get -y install python-pip
sudo pip install docker-compose

git clone https://github.com/sharelatex/sharelatex.git
cd sharelatex
sudo  docker-compose up

실행결과

jaynux@jaynux-desktop:~/ShareLaTex/sharelatex$ sudo docker-compose up
Starting redis ... 
Starting mongo ... 
Starting redis
Starting mongo ... done
Starting sharelatex ... 
Starting sharelatex ... done
Attaching to redis, mongo, sharelatex
redis         | 1:C 21 Nov 05:11:06.228 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis         | 1:C 21 Nov 05:11:06.228 # Redis version=4.0.2, bits=64, commit=00000000, modified=0, pid=1, just started
redis         | 1:C 21 Nov 05:11:06.228 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis         | 1:M 21 Nov 05:11:06.229 * Running mode=standalone, port=6379.
...
...
cture: "x64", version: "4.4.0-101-generic" }, platform: "Node.js v6.11.3, LE, mongodb-core: 2.1.9" }
sharelatex    | Nov 21 05:17:01 994814b4c50a /USR/SBIN/CRON[331]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)

docker-compose.yml파일을 보면 내부에 port번호가 적혀 있다.
기본적으론 80번이다.

따라서 웹 주소창에 IP주소:Port번호로 입력한다.

아래와 같이 나오면 성공이다.

계정 생성

Admin 계정

원하는 계정 이메일로 생성 후 만들어진 링크로 접속해서 비밀번호를 설정 하면 된다.

docker exec sharelatex /bin/bash -c "cd /var/www/sharelatex; grunt user:create-admin --email joe@example.com"

일반 사용자 계정 생성

web에서 admin 계정으로 접속한 다음 상단의 admin을 누르면
일반 계정을 생성 할 수 있는 창이 나온다.

Latex 업데이트

효율적인 배포를 위해서 sharelatex에는 최소한의 tex package만 설치되어 있다. 약 20개 정도이다.
당연히 5~10GB 정도의 추가적인 package들의 설치가 필요하다.

최신 package들을 사용할려면 업데이트 해야한다.

docker exec sharelatex tlmgr update -self
docker exec sharelatex tlmgr install scheme-full

실행 결과

tlmgr: package repository http://ctan.mirrors.hoobly.com/systems/texlive/tlnet (verified)
tlmgr: saving backups to /usr/local/texlive/2017/tlpkg/backups
[1/1, ??:??/??:??] update: texlive.infra [343k] (45234 -> 45305) ... done
running mktexlsr ...
done running mktexlsr.
tlmgr: package log updated: /usr/local/texlive/2017/texmf-var/web2c/tlmgr.log
jaynux@jaynux-desktop:~$ 
jaynux@jaynux-desktop:~$ sudo docker exec sharelatex tlmgr install scheme-full
tlmgr: package repository http://ctan.mirrors.hoobly.com/systems/texlive/tlnet (verified)
[1/3428, ??:??/??:??] install: 12many [376k]
[2/3428, 00:09/16:24:09] install: 2up [66k]
...
..
[3429/3429, 04:04:00/04:04:00] install: scheme-full [1k]
running mktexlsr ...
done running mktexlsr.
running mtxrun --generate ...
done running mtxrun --generate.
running updmap-sys ...
done running updmap-sys.
...
...
tlmgr: package log updated: /usr/local/texlive/2017/texmf-var/web2c/tlmgr.log

업데이트 전 용량

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
mongo                   latest              d22888af0ce0        2 weeks ago         361MB
redis                   latest              8f2e175b3bd1        2 weeks ago         107MB
sharelatex/sharelatex   latest              bffb5a850d01        2 months ago        2.4GB

jaynux@jaynux-desktop:~/ShareLaTex/sharelatex$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            3.9G     0  3.9G   0% /dev
tmpfs           795M  9.7M  786M   2% /run
/dev/sdb6       102G   10G   87G  11% /
tmpfs           3.9G  176K  3.9G   1% /dev/shm
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/sda1       932G  125G  808G  14% /sda1
tmpfs           795M   56K  795M   1% /run/user/1000

업데이트 후 용량

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
mongo                   latest              d22888af0ce0        2 weeks ago         361MB
redis                   latest              8f2e175b3bd1        2 weeks ago         107MB
sharelatex/sharelatex   latest              bffb5a850d01        2 months ago        2.4GB

Filesystem      Size  Used Avail Use% Mounted on
udev            3.9G     0  3.9G   0% /dev
tmpfs           795M  9.7M  786M   2% /run
/dev/sdb6       102G   16G   82G  16% /
tmpfs           3.9G  176K  3.9G   1% /dev/shm
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/sda1       932G  125G  808G  14% /sda1
tmpfs           795M   56K  795M   1% /run/user/1000

위와 같이 docker image 용량은 변화가 없지만 HDD 용량은 5GB가량이 늘었다.
결국 latex package 파일들이 docker에 저장된 상태가 아니다.

이 상태에서 .yml파일을 수정하고 docker-compose up을 실행하면 image가 recreating되고 latex file들이 모두 날라간다.
아래와 같은 메시지가 실행되면서..

Recreating sharelatex ... 
Recreating sharelatex ... done

Latex 업데이트후 docker commit

Docker container의 변화가 permanent하지 않기 때문에 restart하면 결과를 잃어 버린다.
왜냐하면 데이터 디렉토리가 containier의 외부에 위치하기 때문이다.
permanent하게 이 변경 사항을 저장하려면 commit을 해야 한다.

current id 파악

sudo docker ps

패키지 업데이트 내용을 반영한 새로운 docker image를 생성한다.

sudo docker commit -m "installing all latex packages" container-ID ImageName

명령어는 아래와 같다.

jaynux@jaynux-desktop:~/ShareLaTex/sharelatex$ sudo docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                NAMES
03a947ea942a        sharelatex/sharelatex   "/sbin/my_init"          7 hours ago         Up 7 hours          0.0.0.0:80->80/tcp   sharelatex
9fe256a837e3        mongo                   "docker-entrypoint.s…"   7 hours ago         Up 7 hours          27017/tcp            mongo
3e0b3a7561b3        redis                   "docker-entrypoint.s…"   7 hours ago         Up 7 hours          6379/tcp             redis

jaynux@jaynux-desktop:~/ShareLaTex/sharelatex$ sudo docker commit -m "installing all latex packages" 03a947ea942a sharelatex/sharelatex:v3
sha256:54065502e1795908ba688f2209edc3c9b38e6706e156fe07865370d3ce335523

jaynux@jaynux-desktop:~/ShareLaTex/sharelatex$ sudo docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
sharelatex/sharelatex   v3                  54065502e179        3 minutes ago       7.5GB
mongo                   latest              d22888af0ce0        2 weeks ago         361MB
redis                   latest              8f2e175b3bd1        2 weeks ago         107MB
sharelatex/sharelatex   latest              bffb5a850d01        2 months ago        2.4GB

위와 같이 정상적으로 commit되서 v3가 생성된 것을 볼 수 있다.

추후에 실행을 위해서 docker-compose.yml에서 image 위치를 변경한다.

version: '2'
services:
    sharelatex:
        restart: always
        image: sharelatex/sharelatex:v3

참고자료: full functionalities 설치 방법

SMTP 설정

메일 설정을 해야 계정 초대 메일이나 디렉토리 공유 메일이 정상적으로 발송된다.

        SHARELATEX_EMAIL_FROM_ADDRESS: "team@sharelatex.com"
        SHARELATEX_EMAIL_SMTP_HOST: smtp.gmail.com
        SHARELATEX_EMAIL_SMTP_PORT: 465
        SHARELATEX_EMAIL_SMTP_SECURE: 'true'
        SHARELATEX_EMAIL_SMTP_USER: 본인 ID
        SHARELATEX_EMAIL_SMTP_PASS: 본인 비밀번호
        SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: 'true'
        SHARELATEX_EMAIL_SMTP_IGNORE_TLS: 'false'
        SHARELATEX_CUSTOM_EMAIL_FOOTER: "<div>This system is run by me</div>"

참고자료

도메인 생성을 위한 SSL 설치

공식 사이트에선 nginx를 이용해서 설치를 해야한다고 한다.

sudo apt-get install nginx
nginx -v

nginx는 Apache처럼 웹서버 환경을 만들어주는 소프트웨어이다.
웹 서버란 전체 서버 아키텍처의 앞단에서 HTTP요청에 따라서 그 요청을 넘기고 그에 해당하는 file혹은 리소스를 넘겨주는 역할을 하는 부분입니다.

// 시작
$ sudo service nginx start
$ sudo systemctl start nginx
$ sudo /etc/init.d/nginx start

// 재시작
$ sudo service nginx restart
$ sudo systemctl restart nginx
$ sudo /etc/init.d/nginx restart

// 중지
$ sudo service nginx stop
$ sudo systemctl stop nginx
$ sudo /etc/init.d/nginx stop

// 상태
$ sudo service nginx status
$ sudo systemctl status nginx

// 설정 reload
$ sudo service nginx reload
$ sudo systemctl reload nginx
$ sudo nginx -s reload

// configuration file syntax check
$ sudo nginx -t

해야할 것들

  1. sharelatex port 변경
  2. nginx로 특정 port로 변경된 sharelatex으로 연결되게 하기
  3. SSL 설치해서 nginx로 연동 시키기

참고자료
nginx에 HTTPS/SSL 적용하기

uBuntu에 nginx 설치하기

Proxy에 대하여

우분투, 민트 nginX에 reverse proxy 사용하기


'논문 작성 > LaTex' 카테고리의 다른 글

Latex 줄 번호 삽입  (0) 2016.12.23
TextStudio 문법 검사 기능 사용  (0) 2016.06.29
TexLive 2016 설치법  (0) 2016.06.15
Latex 에 하이퍼링크 추가방법  (0) 2016.05.30
citation 번호 합치기  (0) 2016.04.22

Docker를 ubuntu 16.04 LTS에 설치하기


https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script

가장 쉽게 설치 할수 있는 방법은 convenience script를 이용하는 것이다.
get.docker.com test.docker.com에서 각각 stable 버전과 testing 버전을 Docker Comunity Edition으로 받을 수 있다.
이러한 자동 스크립트 방식은 하나씩 설치하는 것이 비해서 간편하지만 아래와 같은 불이익이 있음을 공식 홈페이지에서 언급한다.

  • The scripts require root or sudo privileges in order to run. Therefore, you should carefully examine and audit the scripts before running them.
  • The scripts attempt to detect your Linux distribution and version and configure your package management system for you. In addition, the scripts do not allow you to customize any installation parameters. This may lead to an unsupported configuration, either from Docker’s point of view or from your own organization’s guidelines and standards.
  • The scripts install all dependencies and recommendations of the package manager without asking for confirmation. This may install a large number of packages, depending on the current configuration of your host machine.
  • Do not use the convenience script if Docker has already been installed on the host machine using another mechanism.

스크립트 실행

jaynux@jaynux-desktop:~$ curl -fsSL get.docker.com -o get-docker.sh
jaynux@jaynux-desktop:~$ sudo sh get-docker.sh

설치됨

# Executing docker install script, commit: 11aa13e
+ sh -c apt-get update -qq >/dev/null
+ sh -c apt-get install -y -qq apt-transport-https ca-certificates curl software-properties-common >/dev/null
+ sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | apt-key add -qq - >/dev/null
+ sh -c echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial edge" > /etc/apt/sources.list.d/docker.list
+ [ ubuntu = debian ]
+ sh -c apt-get update -qq >/dev/null
+ sh -c apt-get install -y -qq --no-install-recommends docker-ce >/dev/null
+ sh -c docker version
Client:
 Version:      17.11.0-ce
 API version:  1.34
 Go version:   go1.8.3
 Git commit:   1caf76c
 Built:        Mon Nov 20 18:37:39 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.11.0-ce
 API version:  1.34 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   1caf76c
 Built:        Mon Nov 20 18:36:09 2017
 OS/Arch:      linux/amd64
 Experimental: false
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker your-user

Remember that you will have to log out and back in for this to take effect!

WARNING: Adding a user to the "docker" group will grant the ability to run
         containers which can be used to obtain root privileges on the
         docker host.
         Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
         for more information.

참고문헌

초보를 위한 도커 안내서 -설치하고 컨테이너 실행하기


R Factor 검색


factor로 구성된 vector가 있다고 가정 한다.

%in

'a' %in% v

any()
any('a' == v)
[1] TRUE

is.element()
is.element('a',v)

첫 번째 매치 index 반환

match('a',v)

모든 일치하는 index 반환

which('a' == v)
[1] 2 4


'AI > R Basic' 카테고리의 다른 글

Data Transformation: dplyr package  (0) 2018.04.14
두 개 데이터 프레임 병합, 빼기  (0) 2018.03.04
RStudio v1.1 Released  (0) 2017.10.10
rJava load 에러  (0) 2017.04.16
Swapping columns in a matrix  (0) 2017.03.31

NotificationListenerService 이상 종료 문제 및 디버깅 방법


Debugging 방법

notification access를 끈다음 debug rerun을 하고 다시 enable하면 잘된다.

원글: https://stackoverflow.com/questions/37774499/cant-use-debugger-with-notificationlistenerservice

NotificationListenerService가 호출되지 않는 문제

Android caching 문제라고 한다.
즉, 장치에 app을 업로드 할 때 OS는 해당 서비스를 notification manager에 connection하고 있으므로 다시 앱을 재실행 한다고해서 그 서비시를 재 연결하지 않는다. 따라서 그 후로 이상하게 onPosted나 onRemoved 같은 callback 함수가 호출이 안되는 것이다.

해결 방법: 서비스 이름을 변경 한다. 앱을 pushing하기 전에

onListenerConnected 해당 API를 이용하면 해당 Listener가 연결 되었는지를 알 수 있다.

원글: https://stackoverflow.com/questions/33530807/why-is-this-notificationlistenerservice-not-working

Cannot get the NotificationListenerService class to work

If your APK/binary changes and your NotificationListenerService stops:

  • Rebooting fixes it.
  • Going back in to Notification access and disabling and re-enabling your app it fixes it.

원글: https://stackoverflow.com/questions/17911883/cannot-get-the-notificationlistenerservice-class-to-work

Ask the user to enable the setting (Notification Access) if needed

if (!Utilities.hasNotificationAccess(this)) 
{
     Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
     startActivity(intent);
     Log.i(TAG,"hasNotificationAccess NO");
}
else
{
    Log.i(TAG,"hasNotificationAccess YES");

    // without this startService, it never works with Android 4.4...
    // but this is not needed in Android 6... 
    Intent mServiceIntent = new Intent(this, NLService.class);
    startService(mServiceIntent);
}


Firebase 설정 방법


홈페이지에 워낙 잘 나와있지만 그냥 정리 차원에서 다뤄 본다.

앱등록

debug key 값 알아내기

  • Gradle-> Tasks-> android -> signingReport -> SHA1 key 값

그 다음 아래와 같이 등록한다.

구성 파일 다운로드

  • google-services.json 다운로드 한다음 프로젝트에다가 넣어준다.

주의할 것은 app폴더안에다가 넣어야 한다는 것이다. build가 아니다. 스크린샷을 보고 잘 해야 한다.

Firebase SDK 추가

Gradle Scripts의 첫 번째 build.gradle dependencies라는 항목안에다가 classpath 추가한다.

  • 'com.google.gms:google-services:3.1.0'의 내용을 추가 한다.

두 번째 build.gradle에서는 맨 하단에다가 apply plugin: 'com.google.gms.google-services'을 추가한다.

사용 가능한 라이브러리 추가

원하는 Firebase 기능 구현에 따라 아래의 라이브러리를 gradle에 추가한다.

  • com.google.firebase:firebase-core:11.0.4 애널리틱스
  • com.google.firebase:firebase-database:11.0.4 실시간 데이터베이스
  • com.google.firebase:firebase-storage:11.0.4 저장소
  • com.google.firebase:firebase-crash:11.0.4 오류 보고
  • com.google.firebase:firebase-auth:11.0.4 인증
  • com.google.firebase:firebase-messaging:11.0.4 클라우드 메시징
  • com.google.firebase:firebase-config:11.0.4 원격 구성
  • com.google.firebase:firebase-invites:11.0.4 초대 및 동적 링크
  • com.google.firebase:firebase-ads:11.0.4 AdMob
  • com.google.firebase:firebase-appindexing:11.0.4 앱 색인 생성
  • com.google.firebase:firebase-perf:11.0.4 성능 모니터링


The device 'SAMSUNG_Android' was unable to connect to its ideal host controller.

An attempt will be made to connect this device to the available host controller. This might result in undefined behavior for this device.


그냥 USB 2.0에해서 해결 했다.


https://communities.vmware.com/thread/446744

RStudio v1.1 Released


R Studio가 정식버전으로 2017-10-09일자로 v1.1이 업데이트 되었습니다.

위와 같이 Dark 테마가 기본으로 설정됩니다. 또한 terminal이 기본으로 탑제 되었습니다.

기타 변경사항의 Highlight는 아래와 같습니다.

  • connection tab: 다양한 database들과 쉽게 연결 하여 탐색 할 수 있습니다.
  • terminal tab: shell을 지원합니다.
  • object explorer: R data 구조와 객체를 좀 더 깊이 있게 탐색 할 수 있습니다.
  • dark theme: 현대적인 Theme을 사용 할 수 있습니다.
  • 다양한 약간의 향상과 버그 수정

Pro version은 좀 더 다양한 기능을 제공 받습니다.

설치 방법

  1. 공식사이트 URL에 접속후 운영체제에 맞는 설치파을을 다운로드 한다.

  2. 기존 설치자의 경우도 알아서 uninstall후 재설치가 자동으로 이뤄 진다.

출처

R Studio 공식 블로그


'AI > R Basic' 카테고리의 다른 글

두 개 데이터 프레임 병합, 빼기  (0) 2018.03.04
R Factor 검색  (0) 2017.11.13
rJava load 에러  (0) 2017.04.16
Swapping columns in a matrix  (0) 2017.03.31
R Notebook  (0) 2016.11.02

APK 바이너리 수정후 리패키징(repack)


APK 추출 방법

사용자 앱의 apk 저장 위치는 /data/packageName이다.
system 앱의 경우 apk 저장위치는 /system이다.
각종 앱 데이터 파일들은 (Database, so 파일)
/data/data/<appname>에 존재 한다.

이런 정보들은 기본적으로 adb shell pm 명령어를 통해서도 알 수 있다.

root@jemin-virtual-machine:~/AndroidStudioProjects# adb -d shell pm list packages -f -e com.mobisystems.android.notifications
package:/data/app/com.mobisystems.android.notifications-1/base.apk=com.mobisystems.android.notifications
# -f // see their associated file.
# -e // filter to only show enabled packages.

Resource XML 파일 분석 및 smali/baksmali 수준 분석

java 코드 생성과는 상관 없다.

APKTool

단순히 unzip을 할경우 .xml 파일과 같은 것들이 정상적으로 그 값을 표시하지 못하고 있다.
따라서 apktool를 이용해서 압축을 해제해야 한다.
apk를 디컴파일해서 구조를 살펴 볼 수 있도록 로우 레벨에서 도움을 준다. pirate 목적으로는 쓰지말라고 한다.

공식사이트: http://ibotpeaches.github.io/Apktool/

설치방법
1.5.2 이상의 apktool를 사용하기 위해서는 Java 1.7 이상이 필요하다.
java -version 을 실행해서 자신의 PC에 자바가 설치되어 있는지 확인 하자.

상세한 설치방법은 공식 사이트 참조
현재 사용한 버전은 Apktool v2.0.2이다.
2015.10.12일에 release된 것이다.
jar 파일 다운받고 -> script 다운받고 -> apk d test.apk하면 depack 된다.

Linux:

  • Download Linux wrapper script (Right click, Save Link As apktool)
  • Download apktool-2 (find newest here)
  • Make sure you have the 32bit libraries (ia32-libs) downloaded and installed by your linux package manager, if you are on a 64bit unix system.
  • (This helps provide support for the 32bit native binary aapt, which is required by apktool)
  • Rename downloaded jar to apktool.jar
  • Move both files (apktool.jar & apktool) to /usr/local/bin (root needed)
  • Make sure both files are executable (chmod +x)
  • Try running apktool via click

사용방법

$ apktool d testapp.apk
I: Using Apktool 2.0.0 on testapp.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: 1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
$

APK Studio

Apkstudio: http://www.vaibhavpandey.com/apkstudio/
GUI로 구성된 것이다.
smali code 할 때 하이라이팅이 되어서 편하다.
depackaging / re-packaging 모두 버튼으로 가능 하다.

APK Manager

ApkManager : http://forum.xda-developers.com/showthread.php?t=1310151
여러 기능이 있다.

Dalvik Executable (.dex) 파일을 jar 파일로 변경

관련 사이트
github 공식사이트
bitbucket:
사용법
wiki

UserGuide

  1. Install JDK7
// For Ubuntu 
sudo apt-get install openjdk-7-jre
  1. Download dex2jar
    2015.03.16일자로 dex2jar-2.0.zip이 최신 버전이다.

  2. 압축을 풀어서 실행 스크립트 파일을 생성한다.

# For Linux
unzip -x dex2jar-version.zip -d /home/panxiaobo
  1. dex2jar를 이용해서 xx.apk파일을 xx-dex2jar.jar로 변환 한다.
  • bat은 윈도우
  • sh는 리눅스
# For Linux, Mac OSX, Cygwin
# 경로 /root/repackToolBox/reversingTools/dex2jar-2.0
d2j-dex2jar.sh xx.apk
  1. use a decompiler to view the source.
    아래 세개중 하나의 decopiler를 이용해서 source code를 볼 수 있다. 필자는 2012년 부터 개발된 jd-gui를 사용한다.
    jd-gui: http://jd.benow.ca/
    JAD: http://varaneckas.com/jad/
    Procyon: https://bitbucket.org/mstrobel/procyon

wiki 정리 (FAQ)
이것은 decomplier 인가?
-> 이것은 단순히 .dex format을 또다른 format인 .class format으로 변경 해주는 것이다. 이 또한 하나의 binary format 이다. 이것은 source code가 아니다.

JD-GUI 사용해서 소스코드 보기

jar파일을 decompile해서 java source code 레벨로 보여준다.
smali code를 바로 수정하기 어렵기 때문에 이것을 이용해서 해당 코드를 찾는다.

java용 GUI 도구를 설치하자.
eclipse와 InteliJ용도 있다.

File>Save All Sources를 선택하면 소스코드로 저장 할 수 있다.
이것과 이전에 Apktool로 unpack한것을 같이 묶어서 Eclipse로 분석하면, 좀 더 편하게 작업할 수 있다.

Building (APK Repack)

수정한 APK를 다시 repack 하는 방법이다.

# The build option can be invoked either from b or build like shown below
# builds a directory into new.apk
$ apktool b directory_name -o new.apk

모든 build 관련 명령어

$ apktool b foo.jar.out
// builds foo.jar.out folder into foo.jar.out/dist/foo.jar file

$ apktool build foo.jar.out
// builds foo.jar.out folder into foo.jar.out/dist/foo.jar file

$ apktool b bar
// builds bar folder into bar/dist/bar.apk file

$ apktool b .
// builds current directory into ./dist

$ apktool b bar -o new_bar.apk
// builds bar folder into new_bar.apk

$ apktool b bar.apk
// WRONG: brut.androlib.AndrolibException: brut.directory.PathNotExist: apktool.yml
// Must use folder, not apk/jar file

Signing Your App Manullay without Android Studio

SDK와 JDK에 있는 standard tool을 이용해서 apk를 signing 할 수도 있다.

1. Generate a private key using keytool

$ keytool -genkey -v -keystore my-release-key.keystore
-alias alias_name -keyalg RSA -keysize 2048 -validity 10000

2. Compile your app in release mode to obtain an unsigned APK

3. Sign your app with your private key using jarsigner
아래의 명령어를 이용할경우 하나의 APK에 대해서 서로다른 sign으로 여러번 서명을 할 수 있게 된다.

$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1
-keystore my-release-key.keystore my_application.apk alias_name

4. Verify that your APK is signed
아래의 명령어를 통해서 해당 App이 정상적으로 해당 Key로 sign 되었는지를 알 수 있다.

$ jarsigner -verify -verbose -certs my_application.apk

저장 위치: \jdk\bin

5. Align the final APK package using zipalign
zipalign을 이용해서 모든 비압축 데이터는 특정한 bye alignment로 시작하게 된다. 이를 통해서 해당 앱에 의해서 사용되는 RAM의 양을 줄일 수 있다.

$ zipalign -v 4 your_project_name-unaligned.apk your_project_name.apk

추후 확인 사이트

http://www.cs.bham.ac.uk/~axm514/NotifyMe/#

악성코드 정적분석 네이버블로그: http://suspected.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%85%EC%84%B1%EC%BD%94%EB%93%9C%EC%A0%95%EC%A0%81-%EB%B6%84%EC%84%9D-%EB%B0%A9%EB%B2%95

APK Studio 관련: http://secuinfo.tistory.com/entry/Android-Smali

Smali: https://github.com/JesusFreke/smali
http://0x616b616d61.tistory.com/21
http://strawberryit.tistory.com/142
http://secuinfo.tistory.com/entry/Android-Smali


'Computer Science > Reverse Engineering' 카테고리의 다른 글

smali/baksmali 수준의 코드 수정  (3) 2015.12.16

Ch 09 Recursion and Dynamic Programming


재귀적 방법

  • 상향식과 하향식으로 나눠 진다.

동적 프로그래밍의 예: 피보나치 수
중간과정을 저장하면 그게 동적 프로그래밍이다.

$n^2$ 복잡도의 일반 코드

int fibonacci(int i){
	if(i==0) return 0;
	if(i==1) return 1;
	return fibonacci(i-1) + fibonacci(i-2);
}

Time complexity를 $O(n)$으로 줄이는 코드

int[] fib = new int[max];
int fibonacci(int i){
	if (i==0) return 0;
	if (i==1) return 1;
	if (fib[i] != 0) return fib[i]; // 캐시된 결과 반환
	fib[i] = fibonacci(i-1) + fibonacci(i-2); // 계산 결과 캐시
	return fib[i];
}

동적 프로그래밍을 구현 할 때의 전략은 다음과 같다.

  • 재귀함수를 먼저 구현
  • 캐쉬 부분을 구현


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch02 LinkedList  (0) 2017.08.11
Ch01 Array and Strings  (0) 2017.08.10
Sorting  (0) 2017.08.06
Graph Search  (0) 2017.08.06
String  (0) 2017.07.31

Handler를 이용한 시간제한 기능 구현


BLE Scanning 주기 조절

private static final long SCAN_PERIOD = 1000

private void scanLeDevice(final boolean enable){
	if(enable){
		mHandler.postDelyaed(new Runnable(){
			@Override
			public void run(){
				mScanning = false;
				mBluetoothAdapter.stopLeScan(mLeScanCallback);				
			}
		}, SCAN_PERIOD);
	mScanning = true;
}

백키 두번 이상 연속해서 누를 때만 액티비티 종료

private boolean isBackPressedOnce = false;
private static final long BACKKEY_DELAY = 5000;

@Override
public void onBackPressed(){
	if (isBackPressedOnce){
		super.onBackPressed(); // 종료한다.
	} else {
		Toast.makeText(this, R.string.backpressed_message, Toast.LENGTH_SHORT).show();
		isBackPressedOnce = true; // 한 번 백키 누른것을 저장한다.
		mHandler.postDelayed(timerTask, BACKKEY_DELAY); // 5초후 작업을 지정한다.
	}
}

// 5초가 지나면 한 번 누른 백키를 초기화 한다.
private final Runnable timerTask = new Runnable(){
	@Override
	public void run(){
		isBackPressedOnce = false;
	}

}


'Computer Science > Android Application' 카테고리의 다른 글

NotificationListenerService 이상 종료 문제 및 디버깅 방법  (0) 2017.10.31
Firebase 설정 방법  (0) 2017.10.31
AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20

Java-Builder Pattern


  • Telescoping Pattern은 생성자 overloading을 말하며 인수가 여러개면 가독서이 떨어진다.
  • JavaBeans Pattern은 getter, sgetter 메소드를 이용하는 방법을 말한다. 하지만 중간에 객체 상태가 노출되고 초기화 과정에서 객체를 참조하면 문제가 된다. 또한 언제든지 객체가 변경 될 수 있기 때문에 immutable하지 않다.

이러한 객체 초기화 문제점들을 Builder Pattern으로 해결한다.

public class BuilderPattern { 
    private int a; 
    private int b; 
    private int c; 
    private int d; 
     
    public static class Builder{ 
        private int a; 
        private int b; 
        private int c; 
        private int d; 
         
        public Builder a(int a){ 
            this.a = a; 
            return this; 
        } 
        public Builder b(int b){ 
            this.b = b; 
            return this; 
        } 
        public Builder c(int c){ 
            this.c = c; 
            return this; 
        } 
        public Builder d(int d){ 
            this.d = d; 
            return this; 
        } 
        public BuilderPattern build(){ 
            return new BuilderPattern(this); 
        } 
    } 
    private BuilderPattern(Builder builder){ 
        a = builder.a; 
        b = builder.b; 
        c = builder.c; 
        d = builder.d; 
    } 
}

BuilderPattern builderPattern = new BuilderPattern.Builder().a(1).b(2).c(3).d(4).build();


출처

http://cleancodes.tistory.com/15


'Computer Science > Design Pattern' 카테고리의 다른 글

Java-Singleton Pattern  (0) 2017.08.22

Java-Singleton Pattern


  1. 생성자를 private으로 만든다. new를 막기 위함이다.
  2. private static final 변수를 내부에서 초기화
public class Singleton{
	private static final Singleton instance = new Singleton();
	private Singleton(){ }
	public static Singleton getInstance(){ return instance; }
}

Lazy Initialization: 필요할 때 객체를 생성해서 자원 낭비를 막는다.

public class Singleton{
	private static Singleton instance;
	private Singleton{}{ }
	
	public synchronized static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

이 경우 getInstance 메소드 호출할 때 마다 동기화를 수행하기 때문에 성능 저하가 심하다.

DCL (Double-Checking Locking)방법

  • 인스턴스가 null인지 확인 한 후, null이면 동기화를 얻고 객체를 생성한다. 그리고 그 다음부터는 동기화를 하지 않아도 된다.
public class Singleton{
	private volatitle static Singleton instance;
	
	private Singleton(){}

	public static Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
	}
}

참고자료

http://cleancodes.tistory.com/14


'Computer Science > Design Pattern' 카테고리의 다른 글

Java-Builder Pattern  (0) 2017.08.22

AIDL과 Remote Service


AIDL(Android Interface Definition Language)은 전에 다뤄본 다른 IDL과 유사합니다. 클라이언트와 서비스가 프로세스간 통신(IPC)을 사용하여 서로 소통하는 데 동의한 프로그래밍 인터페이스를 정의할 수 있습니다. Android에서는 한 프로세스가 다른 프로세스의 메모리에 정상적으로 액세스할 수 없습니다. 따라서 객체들을 운영 체제가 이해할 수 있는 원시 유형으로 해체하고 해당 경계에 걸쳐 마샬링해야 합니다. 이 마샬링을 위해 코드를 작성하는 일은 상당히 지루한 작업인데, Android는 AIDL을 이용해 그 일을 대신 해줍니다.

마샬링: 한 객체의 메모리에서의 표현 방식을 저장 또는 전송에 적합한 다른 데이터 형식으로 변환하는 과정이다. 마샬링은 직렬화와 유사하며 한 오브젝트 여기서는 직렬화 된 오브젝트로 멀리 떨어진 오브젝트와 통신하기 위해 사용된다.

AIDL 인터페이스 정의

AIDL을 사용하여 바인드된 서비스를 생성하려면 다음 단계를 따라야 한다.

  1. aidl 파일 생성

    • 이 파일은 메서드 서명으로 프로그래밍 인터페이스를 정의한다.
  2. 인터페이스 구현

    • Android SDK 도구는 .aidl파일을 기반으로 Java 프로그래밍 언어로 인터페이스를 생성한다. 이 인터페이스는Binder를 확장하고 AIDL 인터페이스로부터 메서드를 구현하는 Stub라는 내부 추상 클래스를 가지고 있다. Stub클래스를 확장하고 메서드를 구현해야 한다.
  3. 클라이언트에게 인터페이스 노출

    • Service를 구현하고 onBind()를 재정의하여 Stub 클래스의 구현을 반환한다.

리모트 바인딩

  • 리모트 바인딩 서비스는 다른 프로세스에서 접근하는 것을 전제로 만들어진다. 따라서 로컬에서만 사용하는 서비스라면 리모트 바인딩 서비스를 굳이 만들 필요가 없다.

생성방법

  • 바인딩한 클라이언트에 제공하는 메서드를 aidl 인터페이스로 작성한 다음에 서비스에서 stub클래스의 추상 메서드를 구현해 주면된다.

aidl 인터페이스와 생성 클래스

package com.cnu.eslab.suite;

interface ITrainingService{
	boolean setServiceMode(String filename);
}

이렇게하면 Android Studio에서는 자동으로build/generated/source/aidl디렉터리에IRemoteService.java가 생성된다.

Service에 Stub 구현

  • Service에서는 추상 클래스인 Stub 구현체를 만든다.
public class RemoteService extends Service{
    @Override
    public IBinder onBind(Intent intent) {
            return mBinder;
    }
	
	ITrainingService.Stub mBinder = new ITrainingService.Stub() {
		
		@Override
		public boolean setServiceMode(String filename) throws RemoteException {
			// TODO Auto-generated method stub
			ResultFileName = filename;
			return true;
		}
	};

위와 같이 Stub의 내부 기능을 구현하면 된다.

클라이언트에서 서비스 바인딩

Activity에서 바인딩해서 사용해야 한다.
bindService()는 바인딩 결과를 비동기로 받기 때문에, 콜백으로 사용할 Service Connection인스턴스를 bindService()메서드에 파라미터로 전달한다.

리모트 서비스 바인딩

ITrainingService counterService;

@Override
pulbic void onCreate(Bundle savedInstanceState){
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	
	conn = new TrainingConnection();
}

private class TrainingConnection implements ServiceConnection {

	public void onServiceConnected(ComponentName className, 
                                   IBinder boundService) {
      counterService = ITrainingService.Stub.asInterface((IBinder)boundService);
    }
	
    //bind service가 연결 해지 됬을 때 실행 된다.
    public void onServiceDisconnected(ComponentName className) {
      counterService = null;
    }
  } 
    @Override
	protected void onResume() {
		// TODO Auto-generated method stub
    	//restorePrefs();
	   	super.onResume();
	   	lock.disableKeyguard();
	   	bindService(serviceIntent, conn, 0);
	}
  • connectionCall: ServiceConnection을 생성한다.
  • Stub.asInterface() 메서드를 통해서 로컬인 경우는 Stub 인스턴스, 리모트인 경우 Proxy 인스턴스가 mIRemoteService에 대입된다.
  • 연결이 끊길 때는 mIRemoteService를 null로 만든다.
  • bindService()에 ServiceConnection을 전달한다.
  • mIRemoteService의 메서드를 호출할 때는 먼저 null인지 체크한다.


'Computer Science > Android Application' 카테고리의 다른 글

Firebase 설정 방법  (0) 2017.10.31
Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16

NDK 사용


NDK를 이용해서 간단한 예제를 개발해 본다.

  • NDK (Native Developement Kit): C/C++로 안드로이드 앱을 개발할 수 있도록 도와주는 도구이다.

  • JNI (Java Native Interface): Java와 Native 언어들 간에 서로 호출하고 호출될 수 있도록 인터페이스를 제공한다.

1. NDK 설치

  • SDK Manager를 통해서 3가지를 설치한다.

    • Android NDK (Native Development Kit): Android에서 C 및 C++ 코드를 사용할 수 있도록 지원하고 네이티브 액티비티를 관리하고 물리적 구성 요소 (예: 센서 및 터치 입력)에 엑세스할 수 있는 플랫폼 라이브러리를 제공하는 툴바이다.
    • CMake: Gradle과 함께 작동하여 네이티브 라이브러리를 빌드하는 외부 빌드 도구이다.ndk-build만 사용하려는 경우에는 이 구성 요소가 필요하지는 않다.
    • LLDB: Android Studio가 네이티브 코드를 디버그하는데 사용하는 디버거이다.
  • File -> Project Structure -> SDK Location -> Android NDK location에 설정 되었는지 확인 한다.

2. Project 생성

평상시와 같이 생성하고 단지 옵션에서 include C/C++항목을 체크해 준다.

  • 그냥 자동으로 샘플 코드를 생성해 준다.

샘플 예제를 실행하면 아래와 같다.

빌드과정

  • cpp: 해당 파일은 stringFromJNI()함수를 제공해서 문자열을 반환하게 된다.
  • External Build Files: 해당 그룹에서 CMake또는 ndk-build용 빌드 스크립트를 확인 할 수 있다. build.gradle 파일이 Gradle에 앱을 빌드하는 방법을 알리는 방법과 유사하게 CMake ndk-build에서 네이티브 라이브러리를 빌드하는 방법을 파악하려면 빌드 스크립트가 필요하게 된다. 새로운 프로젝트의 경우 Android Studio CMake 빌드 스크립트 CMakeLists.txt를 생성하여 모듈의 루트 디렉토리에 배치한다.

샘플 앱 빌드 및 실행

  • Gradle이 외부 빌드 스크립트 CMakeLists.txt를 호출
  • CMake가 이 빌드 스크립트에 포함된 명령을 따라 C++소스 파일 native-lib.cpp를 공유 객체 라이브러리로 컴파일하고 이를 libnative-lib.so로 명명한다. 그러면 Gradle이 이를 APK로 패키징한다.
  • 런타임에 앱의 MainActivity System.loadLibrary()를 사용하여 네이티브 라이브러리를 로드한다. 이제 앱에서 라이브러리의 네이티브 함수 stringFromJNI()를 사용할 수 있다.
  • MainActivity.onCreate()가 Hello from C++를 반환하는 stringFromJNI()를 호출하고 이를 사용하여 TextView를 업데이트 한다.

기존 프로젝트에 C/C++ 코드 추가 (Eclipse Migration 포함)

네이티브 코드를 기존 프로젝트에 추가할 수 있다.

  • 새로운 네이티브 소스 파일 생성

이미 있으면 생략

  • CMake 빌드 스크립트 생성

이미 있으면 생략, ndk-build를 사용하기 위해서 Android.mk가 있는 경우에도 생략

  • CMake 또는 ndk-build 스크립트 파일의 경로를 제공하여 Gradle에 네이티브 라이브러리를 링크 시킨다. 방법은 아래와 같다.
    • Android Studio UI를 이용할 경우 ANdroid View에서 앱 모듈에 마우스 오른쪽 버튼을 클릭한 후 메뉴에서 Link C++ Project with Gradle을 선택한다. CMake또는ndk-build를 선택한다. CMake의 경우 CMakeLists.txt 스크립트 파일을 경로에 설정하고 ndk-build의 경우 Android.mk파일을 지정한다. 설정창은 아래와 같다.

참고문헌

http://yucaroll.tistory.com/1
공식사이트


'Computer Science > Android Application' 카테고리의 다른 글

Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
AIDL과 Remote Service  (1) 2017.08.22
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16

Preference


프리퍼런스를 관리하는 클래스는 SharedPreferences이다.

  • SharedPreferences getSharedPreferences (String name, int mode)

    • 첫 번째 인수는 프레퍼런스를 저장할 XML 파일의 이름
    • mode 인수는 이 파일의 공유 모드로 0이면 읽기 쓰기가 가능 하다.
  • SharedPreferences getPreferences (int mode)

    • 파일 인수가 생략되어 있는데 이 경우 액티비티의 이름과 같은 xml 파일이 생성된다.
  • int getInt (String key, int defValue)

  • String getString (String key, String defValue)

  • boolean getBoolean (String key, boolean defValue)

값을 기록하는 메서드는 내부 클래스인 SharedPreferences.Editor가 제공된다.

  • SharedPreferences.Editor putInt(String key, int value)
  • SharedPreferences.Editor putBoolean(String key, boolean value)
  • SharedPreferences.Editor putstring(String key, String value)
  • SharedPreferences.Editor remove(String key)
  • boolean commit()
  • SharedPreferences.Editor clear()

문자열과 정수를 저장하는 간단한 예제

public class PrefTest extends Activity {
	TextView textName;
	TextView textStNum;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.preftest);

		textName = (TextView)findViewById(R.id.name);
		textStNum = (TextView)findViewById(R.id.stnum);

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		String Name = pref.getString("Name", "이름없음");
		textName.setText(Name);

		int StNum = pref.getInt("StNum",20101234);
		textStNum.setText("" + StNum);
	}

	public void onPause() {
		super.onPause();

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		SharedPreferences.Editor edit = pref.edit();

		String Name = textName.getText().toString();
		int StNum = 0;
		try {
			StNum = Integer.parseInt(textStNum.getText().toString());
		}
		catch (Exception e) {}

		edit.putString("Name", Name);
		edit.putInt("StNum", StNum);

		edit.commit();
	}
}
  • getSharedPreferences메서드로 프레퍼런스 객체를 얻는다.
  • onPause에서 edit메서드로 기록할 데이터를 프레퍼런스에 기록 한다.
  • onCreate에서 객체를 읽어온다.

PreferenceActivity

사용자가 설정값을 입력하고 불러 올 수 있는 UI를 미리 만들어서 제공하는 자동화된 방법이다.
xml만 잘 작성하고 Activity에서 PreferenceActivity만 잘 상속해서 사용하면 된다.

<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
	android:key="age"
	android:title="나이"
	android:summary="너 도대체 몇 살이니?"
	android:defaultValue="19" 
/>
<CheckBoxPreference
	android:key="male"
	android:title="성별"
	android:summary="남자면 체크"
	android:defaultValue="true" 
/>
</PreferenceScreen>
public class PrefActivity extends PreferenceActivity {
	@SuppressWarnings("deprecation")
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.layout.prefactivity);
	}
}

TextPref

xml을 이용한 방법은 parser를 이용해야 하기 때문에 속도가 느리다.
빈번하게 onPause에서 현재 상태를 저장해야 한다면 문제가 발생 한다.

그래서 그냥 단순히 text에 기록하고 불러오는 방법을 사용한다.

테스트 코드

public class TextLogTest extends Activity {
	LinearLayout mLinear;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.textlogtest);

		// onCreate에서 로그 유틸리티 초기화
		TextLog.init(this);
		TextLog.mAppendTime = true;
		TextLog.mReverseReport = true;

		mLinear = (LinearLayout)findViewById(R.id.linear);
		mLinear.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					// 필요할 때 로그 기록
					lg.o("down. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				case MotionEvent.ACTION_MOVE:
					lg.o("move. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				}
				return false;
			}
		});
	}

	// 다음 두 메서드를 디버깅 프로젝트의 엑티비티에 추가한다.
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		TextLog.addMenu(menu);
		return true;
	}

	public boolean onOptionsItemSelected(MenuItem item) {
		if (TextLog.execMenu(item) == true) {
			return true;
		}
		return false;
	}
}
//텍스트 파일에 설정 정보를 저장하는 클래스. 안드로이드의 프레프런스가 너무 느려 새로 만듬
//Ready()를 호출하여 입출력 준비하고 기록할 때는 CommitWrite, 읽기만 했을 때는 EndReady를 호출한다.
class TextPref {
	String mPath;
	StringBuilder mBuf;
	static final String HEADER = "__Text Preference File__\n"; 

	// 생성자로 프레퍼런스의 완전 경로를 전달한다. 
	public TextPref(String Path) throws Exception {
		mPath = Path;
		File file = new File(mPath);
		if (file.exists() == false) {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(HEADER.getBytes());
			fos.close();
		}
	}

	// 설정 파일을 삭제한다.
	public void Reset() {
		File file = new File(mPath);
		file.delete();
	}

	// 버퍼를 준비하여 읽기 및 쓰기 준비를 한다.
	public boolean Ready() {
		try {
			FileInputStream fis = new FileInputStream(mPath);
			int avail = fis.available();
			byte[] data = new byte[avail];
			while (fis.read(data) != -1) {;}
			fis.close();
			mBuf = new StringBuilder(avail);
			mBuf.append(new String(data));
		}
		catch (Exception e) {
			return false;
		}
		return true;
	}

	// 버퍼의 내용을 파일로 기록한다.
	public boolean CommitWrite() {
		File file = new File(mPath);
		try {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(mBuf.toString().getBytes());
			fos.close();
		} 
		catch (Exception e) {
			return false;
		}
		mBuf = null;
		return true;
	}

	// 버퍼를 해제하고 읽기를 종료한다. 변경한 내용은 모두 취소된다.
	public void EndReady() {
		mBuf = null;
	}
	
	// name키의 위치를 검색하여 = 다음 위치를 리턴한다. 없으면 -1을 리턴한다.
	// 우연한 중복 방지를 위해 키 이름앞에 __를 붙인다.
	int FindIdx(String name) {
		String key = "__" + name + "=";
		int idx = mBuf.indexOf(key);
		if (idx == -1) {
			return -1;
		} else {
			return idx + key.length();
		}
	}

	// 문자열 키를 기록한다. 이미 있으면 대체한다.
	public void WriteString(String name, String value) {
		int idx = FindIdx(name);
		if (idx == -1) {
			mBuf.append("__");
			mBuf.append(name);
			mBuf.append("=");
			mBuf.append(value);
			mBuf.append("\n");
		} else {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx, end);
			mBuf.insert(idx, value);
		}
	}

	// 문자열 키를 읽는다. 없으면 디폴트를 리턴한다.
	public String ReadString(String name, String def) {
		int idx = FindIdx(name);
		if (idx == -1) {
			return def;
		} else {
			int end = mBuf.indexOf("\n", idx);
			return mBuf.substring(idx, end);
		}
	}

	// 정수를 읽는다. 일단 문자열 형태로 읽은 후 변환한다.
	public void WriteInt(String name, int value) {
		WriteString(name, Integer.toString(value));
	}

	// 정수를 기록한다. 문자열 형태로 변환하여 기록한다.
	public int ReadInt(String name, int def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Integer.parseInt(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteLong(String name, long value) {
		WriteString(name, Long.toString(value));
	}
	
	public long ReadLong(String name, long def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Long.parseLong(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 진위값은 true, false가 아닌 1, 0으로 기록한다.
	public void WriteBoolean(String name, boolean value) {
		WriteString(name, value ? "1":"0");
	}

	public boolean ReadBoolean(String name, boolean def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return s.equals("1") ? true:false;
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteFloat(String name, float value) {
		WriteString(name, Float.toString(value));
	}

	public float ReadFloat(String name, float def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Float.parseFloat(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 한꺼번에 값을 삽입하기 위해 준비한다. 헤더 작성하고 충분한 버퍼를 할당한다.
	void BulkWriteReady(int length) {
		mBuf = new StringBuilder(length);
		mBuf.append(HEADER);
		mBuf.append("\n");
	}

	// 문자열 형태로 받은 값을 무조건 뒤에 덧붙인다.
	void BulkWrite(String name, String value) {
		mBuf.append("__");
		mBuf.append(name);
		mBuf.append("=");
		mBuf.append(value);
		mBuf.append("\n");
	}

	// 키를 삭제한다. 
	void DeleteKey(String name) {
		int idx = FindIdx(name);
		if (idx != -1) {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx - (name.length() + 3), end + 1);
		}
	}
}

출처

[1] 안드로이드 정복 4판, 김상형


'Computer Science > Android Application' 카테고리의 다른 글

AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
Activity  (0) 2017.08.16

+ Recent posts