* UTF8 문자열 한글자씩 출력하기

 

더보기
    s := "Hello 월드"

	s2 := []rune(s)
	for i := 0; i < len(s2); i++ {
		fmt.Print(string(s2[i], ","))
	}

결과

H,e,l,l,o, ,월,드,

 

* 배열 역순으로 놓기

더보기
arr := [5]int{1, 2, 3, 4, 5}

	for i := 0; i < len(arr)/2; i++ {
		arr[i], arr[4-i] = arr[4-i], arr[i]
	}

	fmt.Println(arr)

* swap (a와 b를 바꾸기)

더보기
a := 1
	b := 2

	a, b = b, a

	fmt.Print("a : ", a, ", b : ", b)

 

* 구조체 선언 및 할당

더보기
package main

import "fmt"

//구조체 선언
type Person struct {
	name string
	age  int
}

//구조체 멤버 함수
func (p Person) PrintName() {
	fmt.Print(p.name)
}

func main() {
	//초기화
	var p Person
	p1 := Person{"Name1", 15}
	p2 := Person{name: "Name2", age: 11}
	p3 := Person{name: "Name3"}
	p4 := Person{}

	fmt.Println(p, p1, p2, p3, p4)

	//멤버 변수 할당
	p.name = "new Name"
	p.age = 100

	fmt.Println(p)

	//멤버 함수 실행
	p.PrintName()
}

* 포인터 타입으로 선언

var p* Person

p := *Person{"Name", 20}

 

 

* 구조체를 멤버로 가지는 구조체

  - 일반함수와 멤버함수의 호출방법

더보기
package main

import "fmt"

//구조체 선언
type Student struct {
	name  string
	class int

	sungjuk Sungjuk
}

type Sungjuk struct {
	name  string
	grade string
}

//멤버함수
func (s Student) ViewSungjuk() {
	fmt.Println(s.sungjuk)
}

//멤버함수 : 입력
func (s Student) InputSungjuk(name string, grade string) {
	s.sungjuk.name = name
	s.sungjuk.grade = grade
}

//일반함수
func ViewSungjuk(s Student) {
	fmt.Println(s.sungjuk)
}

func main() {
	var s Student
	s.name = "철수"
	s.class = 1

	s.sungjuk.name = "수학"
	s.sungjuk.grade = "C"

	//멤버함수 호출
	s.ViewSungjuk()
	//일반함수 호출
	ViewSungjuk(s)

	//아래와 깉이 입력해도 바뀌지 않는다
	s.InputSungjuk("과학", "A")
	s.ViewSungjuk()
}

 

* 그런데, golang에서 구조체의 멤버함수라 해도 Call by Value 방식으로 구조체 인스턴스를 복사하는 방식이라 그냥 대입해서는 값이 바뀌지 않는다

 

* 포인터 기본 사용법

더보기
func main() {
	var a int
	var b int
	var p *int

	p = &a
	a = 3
	b = 2

	fmt.Println("a : ", a)
	fmt.Println("p : ", p)
	fmt.Println("*p : ", *p)

	p = &b

	fmt.Println("a : ", b)
	fmt.Println("p : ", p)
	fmt.Println("*p : ", *p)

}

 

* 포인터의 성질

더보기
func main() {
	var a int
	var b int

	a = 1
	b = 1

	IncVal(a)
	IncPointer(&b)

	fmt.Println("a : ", a)
	fmt.Println("b : ", b)
}

func IncVal(x int) {
	x++
}

func IncPointer(x *int) {
	*x = *x + 1
}

 

 

* 가비지 콜렉터 또는 허상참조(dangling reference) : C/C++과 달리 참조 값이 0이 될때까지 메모리 해제가 안된다

더보기
package main

import "fmt"

func main() {
	var p *int
	p = f1()
	fmt.Printf("P : %d", *p)
}

func f1() *int {
	a := 3
	return &a
}

 

 

 

 

 

 

강좌 보면서 따라한 내용을 혹시나 해서 기록해본다

 

package main //이 패키지의 이름은 main이다
//main이라는 package는 '프로그램 시작점'이라는 약속


//아래의 package를 가져와서 포함한다
//fmt를 포함한 아래 목록은 표준 package
import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

//func : 함수
//main 함수는 시작점이다
func main() {
	fmt.Print("Input Number 1 : ")
	//표준 입력을 받기 위한 객체
	reader := bufio.NewReader(os.Stdin)

	//input string과 error를 리턴 받는데, error는 변수에 담지 않는다
	line, _ := reader.ReadString('\n')
    //공백 제거
	line = strings.TrimSpace(line)
    //정수 변환
	n1, _ := strconv.Atoi(line)

	fmt.Print("Input Number 2 : ")
	line, _ = reader.ReadString('\n')
	line = strings.TrimSpace(line)
	n2, _ := strconv.Atoi(line)

	fmt.Printf("Input Number : %d %d", n1, n2)

	fmt.Print("Input Operator : ")
	line, _ = reader.ReadString('\n')
	line = strings.TrimSpace(line)

	if line == "+" {
		fmt.Printf("%d + %d = %d", n1, n2, n1+n2)
	} else if line == "-" {
		fmt.Printf("%d - %d = %d", n1, n2, n1-n2)
	} else if line == "*" {
		fmt.Printf("%d * %d = %d", n1, n2, n1*n2)
	} else if line == "/" {
		fmt.Printf("%d / %d = %d", n1, n2, n1/n2)
	} else {
		fmt.Println("Bad Input!!!")
	}

	switch line {
	case "+":
		fmt.Printf("%d + %d = %d", n1, n2, n1+n2)
	case "-":
		fmt.Printf("%d - %d = %d", n1, n2, n1-n2)
	case "*":
		fmt.Printf("%d * %d = %d", n1, n2, n1*n2)
	case "/":
		fmt.Printf("%d / %d = %d", n1, n2, n1/n2)
	default:
		fmt.Println("Bad Input!!!")
	}

}

 

go는 case 문 안에 수식이 들어가도 되고,

switch에 공백이 있으면 true가 기본 값이다

package main

import "fmt"

func main() {
	x := 30

	switch {
	case x > 20:
		fmt.Println("X는 20보다 크다")
	case x < 20:
		fmt.Println("X는 20보다 작다")
	default:
		fmt.Println("Dfault")
	}
}

 

for 문은 다음과 같이 쓴다

package main

import "fmt"

func main() {

	i := 0
	for i < 10 {
		if i > 0 {
			fmt.Print(",")
		}
		fmt.Print(i)
		i++
	}
	fmt.Println()

	for j := 0; j < 10; j++ {
		if j > 0 {
			fmt.Print(",")
		}

		fmt.Print(j)
	}
	fmt.Println()
}

조건문에 공백이면 true이다(무한루프)

break, continue등도 사용가능

package main

import "fmt"

func main() {

	i := 0
	for {
		if i > 0 {
			fmt.Print(",")
		}
		fmt.Print(i)

		i++
		if i > 9 {
			break
		}
	}
	fmt.Println()

	for j := 0; j < 10; j++ {

		if j == 3 {
			continue
		}

		if j > 0 {
			fmt.Print(",")
		}

		fmt.Print(j)
	}
	fmt.Println()
}

 

* 함수

더보기
//함수명(변수 타입, ) 리턴값
//리턴은 복수개 가능
func add(x int, y int) int {
	return x + y
}

func main() {
	// for i := 0; i < 10; i++ {
	// 	fmt.Printf("%d + %d = %d\n", i, (i + 2), add(i, i+2))
	// }

	a, b := func1(2, 3)
	fmt.Println(a, b)

}

//같은 타입이면 마지막에 붙여도 됨
func func1(x, y int) (int, int) {
	func2(x, y)
	return y, x
}

func func2(x, y int) {
	fmt.Println("func2", x, y)
}

 

 

수신 받은 데이터를 DB에 한번 넣어보려고 한다

 

1. 화면은 다음과 같이 정리했다.

더보기
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnRequest"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="요청하기" />

        <Button
            android:id="@+id/btnStationInsert"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="DB INSERT" />
    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvStation"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

2. 버튼을 누르면

  1) DB가 없으면 DB 생성(createDatabase 함수)

  2) 테이블이 없으면 테이블 생성(createTable 함수)

  3) 1개의 데이터만 요청한다. (총 갯수만 알아낼 목적으로 - RequestItemCount()함수)

더보기
        Button btn2 = findViewById(R.id.btnStationInsert);
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createDatabase(DATABASE_NAME); //데이터베이스 생성
                createTable();                 //테이블 생성

                StringRequest request = new StringRequest(
                        Request.Method.GET,
                        "http://apis.data.go.kr/6260000/BusanBIMS/busStopList?serviceKey=___________&pageNo=1&numOfRows=1",
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                RequestItemCount(response);
                            }
                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.d(TAG, "onErrorResponse : " + error.toString());
                            }
                        }
                ){
                    //요청 파라미터를 처리하는 메소드
                    @Nullable
                    @Override
                    protected Map<String, String> getParams() throws AuthFailureError {
                        //요청 객체가 하나 만들어짐
                        Map<String, String> params = new HashMap<String, String>();
                        //요청 큐에 넣어주면 된다

                        return super.getParams();
                    }
                };
                request.setShouldCache(false);
                requestQueue.add(request);
            }
        });
public void createDatabase(String databaseName){
    if(database == null){
        try{
            database = openOrCreateDatabase(databaseName, MODE_PRIVATE, null);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
public void createTable(){
    try{
        if(database == null) return;
        if(adapter == null)  return;
        String sql = "create table if not exists station "
                   + "(bstopid INTEGER primary key, bstopnm varchar, arsno INTEGER, gpsx double, gpsy  double, stoptype varchar, version INTEGER)";
        database.execSQL(sql);
    }
    catch(Exception e){
        e.printStackTrace();
    }
}

3. 이 데이터의 경우 1개만 요청해도 총 갯수정보가 들어 있으므로 그 정보를 이용해서 모든 데이터를 다시 요청한다

('totalCount' 태그)

  1) 총 데이터 갯수를 알아내고

  2) 1000개씩 분할해서 요청한다(갯수는 조정가능)

더보기
private void RequestItemCount(String response)
{
    int count = ParseStationCount(response);
    int div = 1000;

    int pages = count / div;
    if(count % div > 0)
        pages += 1;

    RequestPage(pages, 1, div);
}
private int ParseStationCount(String response){
    String curr_tag = "";
    try {
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        factory.setNamespaceAware(true);
        XmlPullParser xpp = factory.newPullParser();

        xpp.setInput( new StringReader(response) );
        int eventType = xpp.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            if(eventType == XmlPullParser.START_DOCUMENT) {
            } else if(eventType == XmlPullParser.START_TAG) {
                curr_tag = xpp.getName();
            } else if(eventType == XmlPullParser.END_TAG) {
                curr_tag = "";
            } else if(eventType == XmlPullParser.TEXT) {
                if(curr_tag.equals("totalCount"))
                    return Integer.parseInt(xpp.getText());
            }
            eventType = xpp.next();
        }

    } catch (Exception e) {
        e.printStackTrace();
    }

    return 0;
}

4. 페이지별 요청은 다음과 같이 구현하고, DB에 insert한다

  (DB insert시에는 낱개로 넣는것보다 Transaction을 이용하는 것이 빠른듯)

더보기
private void RequestPage(int total_page, int index_page, int div){
	String requstStr = "http://apis.data.go.kr/6260000/BusanBIMS/busStopList?serviceKey=________________&pageNo="+index_page+"&numOfRows=" + div;

			StringRequest request = new StringRequest(
			Request.Method.GET,
			requstStr,
			new Response.Listener<String>() {
				@Override
				public void onResponse(String response) {
					ArrayList<Station> list = ParseStatonStr(response);
					insertData(list);

					if(index_page + 1 <= total_page){
						Log.d(TAG, "Progress : " + (index_page + 1) +"/" + total_page);
						RequestPage(total_page, index_page + 1, div);
					}
				}
			},
			new Response.ErrorListener() {
				@Override
				public void onErrorResponse(VolleyError error) {

				}
			})
	{
		//요청 파라미터를 처리하는 메소드
		@Nullable
		@Override
		protected Map<String, String> getParams() throws AuthFailureError {
			//요청 객체가 하나 만들어짐
			Map<String, String> params = new HashMap<String, String>();
			//요청 큐에 넣어주면 된다

			return super.getParams();
		}
	};
	request.setShouldCache(false);
	requestQueue.add(request);
}
public void insertData(ArrayList<Station> list){
	try{
		if(database == null) return;
		if(adapter == null)  return;

		String sql = "";
		String sql_insert = "insert or replace into station (bstopid, bstopnm, arsno, gpsx, gpsy, stoptype, version) values (";
		Station station;
		database.beginTransaction();

		for(int i=0; i<list.size(); ++i)
		{
			sql = list.get(i).values() + ", 1)";
			database.execSQL(sql_insert + sql);
		}

		database.setTransactionSuccessful();

	}
	catch(Exception e){
		e.printStackTrace();
	}
	finally{
		database.endTransaction();
	}
}

 

5. 안드로이드 에뮬레이터를 실행시키고, 버튼을 누르면 다음과 같이 1000개씩 잘 얻어온다

6. 저장된 결과는 다음과같이 저장 가능하다

(안드로이드 스튜디오 Device Explorer > 아래 경로의 DB를 찾아가서 마우스 우클릭 > Save As 메뉴 선택 후 저장)

 

7. 저장된 파일을 Sqlite 뷰어 프로그램으로 열어보면 다음과 같이 나타난다

 

 

1. 클래스 생성(Station 정보)

더보기
public class Station {
    public String bstopid;
    public String bstopnm;
    public String arsno;
    public String gpsx;
    public String gpsy;
    public String stoptype;

    public void clear(){
        bstopid = "";
        bstopnm = "";
        arsno   = "";
        gpsx    = "";
        gpsy    = "";
        stoptype = "";
    }

    boolean checkRecvAllData(){
        return bstopid.length()  > 0
            && bstopnm.length()  > 0
            && arsno.length()    > 0
            && gpsx.length()     > 0
            && gpsy.length()     > 0
            && stoptype.length() > 0;
    }
}

참고로 수신 받는 데이터의 형태는 아래와 같다

항목명(영문) 항목명(국문) 항목크기 항목구분 샘플데이터 항목설명
resultCode 결과코드 2 1 00 결과코드
resultMsg 결과메세지 50 1 NORMAL SERVICE 결과메세지
numOfRows 한 페이지 결과 수 4 1 10 한 페이지당 표출 데이터 수
pageNo 페이지 수 4 1 1 페이지 수
totalCount 데이터 총 개수 4 1 1 데이터 총 개수
items 목록   0..n   목록
  arsno 정류소번호 5 1 13708 정류소 번호
  bstopid 정류소아이디 9 1 505780000 정류소 아이디
  bstopnm 정류소명 50 1 부산시청 정류소 이름
  gpsx GPS X좌표 28 1 129.07691415917 GPS X좌표
  gpsy GPS Y좌표 28 1 35.179937855685 GPS Y좌표
  stoptype 정류장구분 4 1 일반
정류장구분

 

2. 수신 받는 루틴은 다음과 같고 ArrayList에 담을 수 있다

더보기
	String curr_tag = "";
	ArrayList<Station> arrStation = new ArrayList<>();
	Station station = new Station();

	try {
		XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
		factory.setNamespaceAware(true);
		XmlPullParser xpp = factory.newPullParser();

		xpp.setInput( new StringReader(response) );
		int eventType = xpp.getEventType();
		while (eventType != XmlPullParser.END_DOCUMENT) {
			if(eventType == XmlPullParser.START_DOCUMENT) {
				//System.out.println("Start document");
			} else if(eventType == XmlPullParser.START_TAG) {
				//시작하는 tag 기억
				curr_tag = xpp.getName();
				if(xpp.getName().equals("item")){
					station.clear();
				}
			} else if(eventType == XmlPullParser.END_TAG) {
				//item 태그 종료시 추가 
				if(xpp.getName().equals("item")){
					if(station.checkRecvAllData())
						arrStation.add(station);
				}
				curr_tag = "";
			} else if(eventType == XmlPullParser.TEXT) {
				//태그 종류별로 기록
				switch(curr_tag)
				{
					case "bstopid": station.bstopid  = xpp.getText(); break;
					case "bstopnm": station.bstopnm  = xpp.getText(); break;
					case "arsno":   station.arsno    = xpp.getText(); break;
					case "gpsx":    station.gpsx     = xpp.getText(); break;
					case "gpsy":    station.gpsy     = xpp.getText(); break;
					case "stoptype":station.stoptype = xpp.getText(); break;
				}
			}
			eventType = xpp.next();
		}

	} catch (Exception e) {
		e.printStackTrace();
	}

 

4. 그러면 RecyclerView에 표현하기 위해서 Adapter를 만들어야 한다

1) Adapter를 만들기 위해서 그전에 ViewHolder를 inner class로 만든다

더보기
public class StationAdapter{
//버스 정류장 id와 이름을 표현할 text view를 찾는다
//화면에 표시하기 위한 메소드 정의( setItem )
static class ViewHolder extends RecyclerView.ViewHolder {
        TextView bus_id;
        TextView bus_nm;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            bus_id = itemView.findViewById(R.id.bus_id);
            bus_nm = itemView.findViewById(R.id.bus_name);
        }

        public void setItem(Station station){
            bus_id.setText(station.bstopid);
            bus_nm.setText(station.bstopnm);
        }
    }


}

2) 그리고 나서 Adapter를 완성한다

  OnCreateViewHolder는 메모리에 생성할 때 쓰여지는 메소드이고, onBindViewHolder는 이미 생성된 ViewHolder를 재사용할 때 사용되는 메소드이다

더보기
public class StationAdapter extends RecyclerView.Adapter<StationAdapter.ViewHolder>{

    ArrayList<Station> items = new ArrayList<Station>();

    public void setItems(ArrayList<Station> items){this.items = items;}

    public void clearItems(){ items.clear(); }
    public void addItem(Station station){ items.add(station); }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View itemView = inflater.inflate(R.layout.station_item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Station item =items.get(position);
        holder.setItem(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView bus_id;
        TextView bus_nm;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            bus_id = itemView.findViewById(R.id.bus_id);
            bus_nm = itemView.findViewById(R.id.bus_name);
        }

        public void setItem(Station station){
            bus_id.setText(station.bstopid);
            bus_nm.setText(station.bstopnm);
        }
    }
}

 

3. 그럼 MainActivity에서 다음과 같은 작업을 진행한다

아래와 같은 절차를 거친다

더보기
//1. adapter 생성
adapter = new StationAdapter();
//2. RecyclerView 변수 할당
rvStation = findViewById(R.id.rvStation);
//3. RecyclerView에 layoutManager 할당
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
rvStation.setLayoutManager(layoutManager);
//4. RecyclerView에 adapter 할당
rvStation.setAdapter(adapter);

//5. item 태그가 시작되면 Station 새로운 메모리 할당
//같은 객체를 계속 쓰면 리스트에 모두 같은 객체를 참조하므로 같은 출력물이 나온다

//6. xml 수신 받기 시작할 때 adapter의 리스트 clear
adapter.clearItems();

//7. 객체 하나가 완성되면 adapter에 add
adapter.addItem(station);

//8. 수신이 완료되면 adapter에 notify
adapter.notifyDataSetChanged();

 

4. 완성 코드는 다음과 같다 (개발자 코드는 숨겼음)

더보기
package com.hdongle.busanbusstationinfotest;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;


public class MainActivity extends AppCompatActivity {

    RecyclerView rvStation;
    static RequestQueue requestQueue;
    String TAG = "STATION LIST";
    StationAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

        adapter = new StationAdapter();
        rvStation = findViewById(R.id.rvStation);
        rvStation.setLayoutManager(layoutManager);
        rvStation.setAdapter(adapter);

        requestQueue = Volley.newRequestQueue(getApplicationContext());

        Button btn = findViewById(R.id.btnRequest);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringRequest request = new StringRequest(
                        Request.Method.GET,
                        "http://apis.data.go.kr/6260000/BusanBIMS/busStopList?serviceKey=__________&pageNo=1&numOfRows=100",
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                //Log.d(TAG, "onResponse : " + response);

                                String curr_tag = "";
                                //ArrayList<Station> arrStation = new ArrayList<>();
                                Station station = new Station();
                                adapter.clearItems();

                                try {
                                    XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                                    factory.setNamespaceAware(true);
                                    XmlPullParser xpp = factory.newPullParser();

                                    xpp.setInput( new StringReader(response) );
                                    int eventType = xpp.getEventType();
                                    while (eventType != XmlPullParser.END_DOCUMENT) {
                                        if(eventType == XmlPullParser.START_DOCUMENT) {
                                            //System.out.println("Start document");
                                        } else if(eventType == XmlPullParser.START_TAG) {
                                            //시작하는 tag 기억
                                            curr_tag = xpp.getName();
                                            if(xpp.getName().equals("item")){
                                                station = new Station();
                                            }
                                        } else if(eventType == XmlPullParser.END_TAG) {
                                            //item 태그 종료시 추가 
                                            if(xpp.getName().equals("item")){
                                                if(station.checkRecvAllData()){
                                                    adapter.addItem(station);
                                                }
                                                    //arrStation.add(station);
                                            }
                                            curr_tag = "";
                                        } else if(eventType == XmlPullParser.TEXT) {
                                            //태그 종류별로 기록
                                            switch(curr_tag)
                                            {
                                                case "bstopid": station.bstopid  = xpp.getText(); break;
                                                case "bstopnm": station.bstopnm  = xpp.getText(); break;
                                                case "arsno":   station.arsno    = xpp.getText(); break;
                                                case "gpsx":    station.gpsx     = xpp.getText(); break;
                                                case "gpsy":    station.gpsy     = xpp.getText(); break;
                                                case "stoptype":station.stoptype = xpp.getText(); break;
                                            }
                                        }
                                        eventType = xpp.next();
                                    }

                                } catch (Exception e) {
                                    e.printStackTrace();
                                }

                                //System.out.println("Count : " + arrStation.size());
                                adapter.notifyDataSetChanged();
                                Toast.makeText(getApplicationContext(), "수신완료", Toast.LENGTH_LONG).show();
                            }

                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.d(TAG, "onErrorResponse : " + error.toString());
                            }
                        }
                ){
                    //요청 파라미터를 처리하는 메소드
                    @Nullable
                    @Override
                    protected Map<String, String> getParams() throws AuthFailureError {
                        //요청 객체가 하나 만들어짐
                        Map<String, String> params = new HashMap<String, String>();
                        //요청 큐에 넣어주면 된다

                        return super.getParams();
                    }
                };
                request.setShouldCache(false);
                requestQueue.add(request);
            }
        });

        //XmlParserCreator  parserCreator;
/*
        XmlParserCreator parserCreator = new XmlParserCreator() {
            @Override
            public XmlPullParser createParser() {
                try {
                    return XmlPullParserFactory.newInstance().newPullParser();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        GsonXml gsonXml = new GsonXmlBuilder()
                .setXmlParserCreator(parserCreator)
                .create();
 */
    }


}

 

 

결과는 다음과 같다. 

허접하긴 한데, 뭔가 나왔다는데 의의를 둔다..ㅎㅎㅎ

 

 

* 데이터 송수신을 위해 Volley라는 라이브러리를 사용한다.

 - 스레드를 이용해서 그냥 만들어도 상관은 없다

 

1. 라이브러리 추가

1) File > Project Structure 메뉴 선택

2. Project Structure 창이 나타나면 Dependencies > app > '+' > Library Dependency 선택

 

3. Add Library Dependency 창이 나타나면 

  com.android.volly

  입력하고 Search 버튼을 누르면 목록이 나오는데, 최신 버전을 선택 후 OK 버튼을 누른다

4. 추가된 volly가 목록에 있는지 확인 후 OK 버튼을 선택한다

* 참고로 build.gradle 에 volley가 추가 되어 있음을 확인 할 수 있다

5. Main Activity에서 Volley를 이용해서 요청하고 수신 받아 로그로 출력해본다

더보기
package com.hdongle.busanbusstationinfotest;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;


public class MainActivity extends AppCompatActivity {

    static RequestQueue requestQueue;
    String TAG = "STATION LIST";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        requestQueue = Volley.newRequestQueue(getApplicationContext());

        Button btn = findViewById(R.id.btnRequest);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringRequest request = new StringRequest(
                        Request.Method.GET,
                        "http://apis.data.go.kr/6260000/BusanBIMS/busStopList?serviceKey=___________________________&pageNo=1&numOfRows=100",
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                Log.d(TAG, "onResponse : " + response);
                            }

                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.d(TAG, "onErrorResponse : " + error.toString());
                            }
                        }
                ){
                    //요청 파라미터를 처리하는 메소드
                    @Nullable
                    @Override
                    protected Map<String, String> getParams() throws AuthFailureError {
                        //요청 객체가 하나 만들어짐
                        Map<String, String> params = new HashMap<String, String>();
                        //요청 큐에 넣어주면 된다

                        return super.getParams();
                    }
                };
                request.setShouldCache(false);
                requestQueue.add(request);
            }
        });
    }


}

6. Manifest.xml에 INTERNET 권한을 추가한다 (2군데)

더보기
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hdongle.busanbusstationinfotest">

    <!-- 추가한 부분 1-->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- 추가한 부분 2 : android:usesCleartextTraffic -->
    <application
        android:usesCleartextTraffic="true"    
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BusanBusStationInfoTest">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

7. 다음과 같이 데이터 수신을 확인할 수 있다

 

1. Manifest.xml

1) 전화번호부

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

 

2. build.gradle

dependencies {

    ----기존에 있던 목록--------------------------------------------
    implementation 'com.github.pedroSG94:AutoPermissions:1.0.3'
}

3. setting.gradle

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        ------기존에 있던 목록-----------------------------------
        maven {url "https://www.jitpack.io"}
    }
}

 

4. 인터페이스 상속

//변경 전
public class MainActivity extends AppCompatActivity

//변경 후
public class MainActivity extends AppCompatActivity implements AutoPermissionsListener

 

5. onCreate

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	//---작성 코드들 ------------------------------
	AutoPermissions.Companion.loadAllPermissions(this, 101);
}

6. Generate

@Override
    public void onDenied(int i, String[] strings) {

    }

    @Override
    public void onGranted(int i, String[] strings) {

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        AutoPermissions.Companion.parsePermissions(this, requestCode, permissions,  this);
    }

* 개발 도구 : 안드로이드스튜디오 Acrtic Fox(2020.3.1 Patch3)

 

1. 공공데이터 사이트 가입

 - URL : https://www.data.go.kr/index.do

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

 

2. 데이터 신청

 - URL : https://www.data.go.kr/data/15092750/openapi.do

  - "활용신청" 버튼을 눌러서 데이터 신청

  - 신청이 승이되면 serviceKey를 부여받음

 

3. API 테스트

  - 브라우저에서 다음의 url을 입력 테스트

  - http://apis.data.go.kr/6260000/BusanBIMS/busStopList?serviceKey=서비스키&pageNo=1&numOfRows=10 

 

4. 안드로이드 스튜디오 프로젝트 생성(프로젝트명 : BusanBusStationInfoTest) 

5. activity_main.xml 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btnRequest"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="요청하기" />

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

6. 레이아웃 생성(station_item.xml)

7. 다음의 image를 res/dawable로 복사

8. 레이아웃을 다음과 같이 구성한다.

  (imageView / id / nm)

 

더보기
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="110dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                app:srcCompat="@drawable/station" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/id"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:gravity="center_horizontal|center_vertical"
                    android:text="TextView" />

                <TextView
                    android:id="@+id/name"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:gravity="center_horizontal|center_vertical"
                    android:text="TextView" />
            </LinearLayout>

        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

 

 

 

 

워낙 안드로이드 문외한이라 제목이 맞는지도 잘 모르겠음

 

아무튼 따라하기 하다가 아래처럼 외부 라이브러리를 추가하고(build.gradle) Sync(코끼리?)를 눌렀더니 문제 발생하고 

 

구글링 시작함..-_-

 

삽질에 삽질을 거듭한 결과 

 

* build.gradle

* setting.gradle

이렇게 하니깐 Sync가 됨

 

아직 원인은 모름... 문외한이라..-_-

 

1. 변수의 최대 최소값 구하기

#include <algorithm>


int val1 = -10;
int val2 = -5;
std::cout << std::min(val1, val2) << std::endl;

/*기본적으로 < 순서인데, 양쪽에 절대값이므로 절대값 기준의 최대 최소를 가져온다*/
std::cout << std::min(-10, -5, [](int a, int b) {
    return std::abs(a) < std::abs(b);
}) << std::endl;

auto pairInt = std::minmax(201, 202);
std::cout << "Min : " << pairInt.first << std::endl;
std::cout << "Max : " << pairInt.second << std::endl;

auto pairSeq = std::minmax({ 2,3,43,5,6,7 });
std::cout << "Min : " << pairSeq.first << std::endl;
std::cout << "Max : " << pairSeq.second << std::endl;

2. 배열에서 최대/최소값 구하기

#include <algorithm>

//std::begin, std::end를 이용
int arr[3] = { 1,2,3 };
auto res_min = std::min_element(arr, arr + 3);
auto res_max = std::max_element(arr, arr + 3);
auto res_min2 = std::min_element(std::begin(arr), std::end(arr));
auto res_max2 = std::max_element(std::begin(arr), std::end(arr));

3. vector의 이동

//1000000개의 vector, 각 항목은 1
std::vector<int> srcVec(1000000, 1);

//vector의 복사가 이루어진다
std::vector<int> dstVec  = srcVec;
//srcVec을 잘라내서 dstVec2에 붙여 넣는다
std::vector<int> dstVec2 = std::move(srcVec);

4. std::funcion, std::bind

    std::function<double(double, double)> mDiv1 = std::bind(diveMe, std::placeholders::_1, std::placeholders::_2);
    std::function<double(double)>         mDiv2 = std::bind(diveMe, 20000, std::placeholders::_1);

    double mDiv1_Result = mDiv1(1, 2);
    double mDiv2_Result = mDiv2(2);

    std::map<const char, std::function<double(double, double)>> tab;
    tab.insert(std::make_pair('+', [](double a, double b) {return a + b; }));
    tab.insert(std::make_pair('-', [](double a, double b) {return a - b; }));
    tab.insert(std::make_pair('*', [](double a, double b) {return a * b; }));
    tab.insert(std::make_pair('/', [](double a, double b) {return a / b; }));

    std::cout << " 3 + 5 : " << tab['+'](3, 5) << std::endl;
    std::cout << " 3 - 5 : " << tab['-'](3, 5) << std::endl;
    std::cout << " 3 * 5 : " << tab['*'](3, 5) << std::endl;
    std::cout << " 3 / 5 : " << tab['/'](3, 5) << std::endl;

 

 

학습시에 특정 시점에 호출될 함수를 등록할 수 있다

 

1.ModelCheckpoint(filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1)

  - 학습 중간중간에 저장할 수 있다.

  - filepath: filepath는 (on_epoch_end에서 전달되는) epoch의 값과 logs의 키로 채워진 이름 형식 옵션을 가질 수 있음.
예를 들어 filepath가 weights.{epoch:02d}-{val_loss:.2f}.hdf5라면, 파일 이름에 세대 번호와 검증 손실을 넣어 모델의 체크포인트가 저장 
* monitor: 모니터할 지표(loss 또는 평가 지표) 

  - val_loss : 줄어드는 것을 모니터링

  - val_accuracy : 커지는 것을 모니터링
* save_best_only: 가장 좋은 성능을 나타내는 모델만 저장할 여부
* save_weights_only: Weights만 저장할 지 여부 

  - True : weight와 bias 값만 저장한다

  - False : layer 구성, 노드 갯수, 노드 정보, activation 정보 등의 값을 같이 저장한다

  - 참고) True를 권장 , SaveWeights / LoadWeights
* mode: {auto, min, max} 중 하나. monitor 지표가 감소해야 좋을 경우 min, 증가해야 좋을 경우 max, auto는 monitor 이름에서 자동으로 유추. 

  - monitor가 val_loss인 경우 min

  - monitor가 val_accuracy인 경우 max

* period 확인 횟수 : 1이면 매번 1epoch시 확인, 3이면 3epoch시 확인

from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.optimizers import Adam

model = create_model()
model.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])

mcp_cb = ModelCheckpoint(filepath='/kaggle/working/weights.{epoch:02d}-{val_loss:.2f}.hdf5', monitor='val_loss', 
                         save_best_only=True, save_weights_only=True, mode='min', period=3, verbose=1)
history = model.fit(x=tr_images, y=tr_oh_labels, batch_size=128, epochs=10, validation_data=(val_images, val_oh_labels),
                   callbacks=[mcp_cb])

> 결과

Epoch 1/10
399/399 [==============================] - 3s 5ms/step - loss: 0.6208 - accuracy: 0.7874 - val_loss: 0.4639 - val_accuracy: 0.8360
Epoch 2/10
399/399 [==============================] - 2s 4ms/step - loss: 0.4271 - accuracy: 0.8485 - val_loss: 0.4034 - val_accuracy: 0.8553
Epoch 3/10
399/399 [==============================] - 2s 4ms/step - loss: 0.3787 - accuracy: 0.8646 - val_loss: 0.3800 - val_accuracy: 0.8649

Epoch 00003: val_loss improved from inf to 0.37997, saving model to /kaggle/working/weights.03-0.38.hdf5
Epoch 4/10
399/399 [==============================] - 2s 4ms/step - loss: 0.3516 - accuracy: 0.8738 - val_loss: 0.3673 - val_accuracy: 0.8676
Epoch 5/10
399/399 [==============================] - 2s 5ms/step - loss: 0.3331 - accuracy: 0.8798 - val_loss: 0.3420 - val_accuracy: 0.8778
Epoch 6/10
399/399 [==============================] - 2s 4ms/step - loss: 0.3127 - accuracy: 0.8871 - val_loss: 0.3651 - val_accuracy: 0.8716

Epoch 00006: val_loss improved from 0.37997 to 0.36512, saving model to /kaggle/working/weights.06-0.37.hdf5
Epoch 7/10
399/399 [==============================] - 2s 4ms/step - loss: 0.3005 - accuracy: 0.8905 - val_loss: 0.3428 - val_accuracy: 0.8756
Epoch 8/10
399/399 [==============================] - 2s 4ms/step - loss: 0.2852 - accuracy: 0.8963 - val_loss: 0.3354 - val_accuracy: 0.8769
Epoch 9/10
399/399 [==============================] - 2s 4ms/step - loss: 0.2810 - accuracy: 0.8963 - val_loss: 0.3265 - val_accuracy: 0.8813

Epoch 00009: val_loss improved from 0.36512 to 0.32655, saving model to /kaggle/working/weights.09-0.33.hdf5
Epoch 10/10
399/399 [==============================] - 2s 5ms/step - loss: 0.2694 - accuracy: 0.9007 - val_loss: 0.3181 - val_accuracy: 0.8841

 

*** 참고로 jupyter notebook에서는 명령어는 느낌표(!) 뒤에 붙이면 된다

!ls -lia
#!rm -rf weight*
#!ls -lia

 

2.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10, verbose=0, mode='auto', min_delta=0.0001, cooldown=0, min_lr=0)
* 특정 epochs 횟수동안 성능이 개선 되지 않을 시 Learning rate를 동적으로 감소 시킴 
* monitor: 모니터할 지표(loss 또는 평가 지표) 
* factor: 학습 속도를 줄일 인수. new_lr = lr * factor 
* patience: Learing Rate를 줄이기 전에 monitor할 epochs 횟수. 
* mode: {auto, min, max} 중 하나. monitor 지표가 감소해야 좋을 경우 min, 증가해야 좋을 경우 max, auto는 monitor 이름에서 유추. 

*** ex)val_loss가 감소하다가 특정구간에서 patience 횟수만큼 감소하지 않고 답보하거나 증가하면 learning rate에 factor를 곱해서 적용한다 

> 코드

from tensorflow.keras.callbacks import ReduceLROnPlateau

model = create_model()
model.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])

rlr_cb = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=3, mode='min', verbose=1)
history = model.fit(x=tr_images, y=tr_oh_labels, batch_size=128, epochs=30, validation_data=(val_images, val_oh_labels),
                   callbacks=[rlr_cb])

> 결과(30번의 epoch를 돌리는데 val_loss가 3번이 상 감소하지 않으면 learning_rate에 0.3을 곱한다

Epoch 1/30
399/399 [==============================] - 3s 5ms/step - loss: 0.6246 - accuracy: 0.7862 - val_loss: 0.4564 - val_accuracy: 0.8412
Epoch 2/30
399/399 [==============================] - 2s 5ms/step - loss: 0.4167 - accuracy: 0.8547 - val_loss: 0.4136 - val_accuracy: 0.8520
Epoch 3/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3772 - accuracy: 0.8668 - val_loss: 0.3806 - val_accuracy: 0.8647
Epoch 4/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3506 - accuracy: 0.8757 - val_loss: 0.3913 - val_accuracy: 0.8581
Epoch 5/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3294 - accuracy: 0.8816 - val_loss: 0.3576 - val_accuracy: 0.8702
Epoch 6/30
399/399 [==============================] - 2s 6ms/step - loss: 0.3130 - accuracy: 0.8857 - val_loss: 0.3492 - val_accuracy: 0.8758
Epoch 7/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2984 - accuracy: 0.8930 - val_loss: 0.3354 - val_accuracy: 0.8804
Epoch 8/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2905 - accuracy: 0.8931 - val_loss: 0.3288 - val_accuracy: 0.8813
Epoch 9/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2795 - accuracy: 0.8974 - val_loss: 0.3295 - val_accuracy: 0.8799
Epoch 10/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2694 - accuracy: 0.9016 - val_loss: 0.3254 - val_accuracy: 0.8834
Epoch 11/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2611 - accuracy: 0.9036 - val_loss: 0.3229 - val_accuracy: 0.8806
Epoch 12/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2530 - accuracy: 0.9064 - val_loss: 0.3449 - val_accuracy: 0.8756
Epoch 13/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2490 - accuracy: 0.9086 - val_loss: 0.3321 - val_accuracy: 0.8802
Epoch 14/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2432 - accuracy: 0.9095 - val_loss: 0.3236 - val_accuracy: 0.8847

Epoch 00014: ReduceLROnPlateau reducing learning rate to 0.0003000000142492354.
Epoch 15/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2120 - accuracy: 0.9214 - val_loss: 0.3065 - val_accuracy: 0.8904
Epoch 16/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2076 - accuracy: 0.9241 - val_loss: 0.3047 - val_accuracy: 0.8919
Epoch 17/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2047 - accuracy: 0.9248 - val_loss: 0.3078 - val_accuracy: 0.8909
Epoch 18/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2013 - accuracy: 0.9267 - val_loss: 0.3150 - val_accuracy: 0.8891
Epoch 19/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1986 - accuracy: 0.9277 - val_loss: 0.3108 - val_accuracy: 0.8904

Epoch 00019: ReduceLROnPlateau reducing learning rate to 9.000000427477062e-05.
Epoch 20/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1894 - accuracy: 0.9317 - val_loss: 0.3063 - val_accuracy: 0.8922
Epoch 21/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1876 - accuracy: 0.9318 - val_loss: 0.3069 - val_accuracy: 0.8938
Epoch 22/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1866 - accuracy: 0.9325 - val_loss: 0.3051 - val_accuracy: 0.8918

Epoch 00022: ReduceLROnPlateau reducing learning rate to 2.700000040931627e-05.
Epoch 23/30
399/399 [==============================] - 2s 6ms/step - loss: 0.1834 - accuracy: 0.9344 - val_loss: 0.3054 - val_accuracy: 0.8933
Epoch 24/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1829 - accuracy: 0.9338 - val_loss: 0.3052 - val_accuracy: 0.8932
Epoch 25/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1825 - accuracy: 0.9343 - val_loss: 0.3046 - val_accuracy: 0.8926

Epoch 00025: ReduceLROnPlateau reducing learning rate to 8.100000013655517e-06.
Epoch 26/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1815 - accuracy: 0.9350 - val_loss: 0.3053 - val_accuracy: 0.8923
Epoch 27/30
399/399 [==============================] - 2s 6ms/step - loss: 0.1813 - accuracy: 0.9349 - val_loss: 0.3053 - val_accuracy: 0.8923
Epoch 28/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1812 - accuracy: 0.9351 - val_loss: 0.3053 - val_accuracy: 0.8928

Epoch 00028: ReduceLROnPlateau reducing learning rate to 2.429999949526973e-06.
Epoch 29/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1808 - accuracy: 0.9351 - val_loss: 0.3054 - val_accuracy: 0.8923
Epoch 30/30
399/399 [==============================] - 2s 5ms/step - loss: 0.1808 - accuracy: 0.9349 - val_loss: 0.3054 - val_accuracy: 0.8924

 

3. EarlyStopping(monitor='val_loss', min_delta=0, patience=0, verbose=0, mode='auto', baseline=None,                                          restore_best_weights=False)
특정 epochs 동안 성능이 개선되지 않을 시 학습을 조기에 중단
monitor: 모니터할 지표(loss 또는 평가 지표)
patience: Early Stopping 적용 전에 monitor할 epochs 횟수.
mode: {auto, min, max} 중 하나. monitor 지표가 감소해야 좋을 경우 min, 증가해야 좋을 경우 max, auto는 monitor 이름에서 유추.

* 예를 들어 loss는 계속 줄어드는데, val_loss는 늘어나는 경우

from tensorflow.keras.callbacks import EarlyStopping

model = create_model()
model.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])

ely_cb = EarlyStopping(monitor='val_loss', patience=3, mode='min', verbose=1)
history = model.fit(x=tr_images, y=tr_oh_labels, batch_size=128, epochs=30, validation_data=(val_images, val_oh_labels),
                   callbacks=[ely_cb])

> 결과 : val_loss가 3번 이상 떨어지지 않으면 중단

Epoch 1/30
399/399 [==============================] - 3s 6ms/step - loss: 0.5900 - accuracy: 0.7998 - val_loss: 0.4971 - val_accuracy: 0.8272
Epoch 2/30
399/399 [==============================] - 2s 5ms/step - loss: 0.4100 - accuracy: 0.8578 - val_loss: 0.4399 - val_accuracy: 0.8404
Epoch 3/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3732 - accuracy: 0.8685 - val_loss: 0.4068 - val_accuracy: 0.8513
Epoch 4/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3482 - accuracy: 0.8766 - val_loss: 0.3620 - val_accuracy: 0.8716
Epoch 5/30
399/399 [==============================] - 2s 6ms/step - loss: 0.3287 - accuracy: 0.8825 - val_loss: 0.3704 - val_accuracy: 0.8672
Epoch 6/30
399/399 [==============================] - 2s 5ms/step - loss: 0.3085 - accuracy: 0.8892 - val_loss: 0.3324 - val_accuracy: 0.8761
Epoch 7/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2992 - accuracy: 0.8906 - val_loss: 0.3522 - val_accuracy: 0.8734
Epoch 8/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2832 - accuracy: 0.8959 - val_loss: 0.3314 - val_accuracy: 0.8804
Epoch 9/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2745 - accuracy: 0.8997 - val_loss: 0.3185 - val_accuracy: 0.8823
Epoch 10/30
399/399 [==============================] - 2s 6ms/step - loss: 0.2674 - accuracy: 0.9015 - val_loss: 0.3168 - val_accuracy: 0.8850
Epoch 11/30
399/399 [==============================] - 2s 6ms/step - loss: 0.2581 - accuracy: 0.9038 - val_loss: 0.3485 - val_accuracy: 0.8728
Epoch 12/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2517 - accuracy: 0.9060 - val_loss: 0.3205 - val_accuracy: 0.8823
Epoch 13/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2423 - accuracy: 0.9096 - val_loss: 0.3110 - val_accuracy: 0.8883
Epoch 14/30
399/399 [==============================] - 2s 6ms/step - loss: 0.2386 - accuracy: 0.9115 - val_loss: 0.3262 - val_accuracy: 0.8877
Epoch 15/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2300 - accuracy: 0.9146 - val_loss: 0.3154 - val_accuracy: 0.8876
Epoch 16/30
399/399 [==============================] - 2s 5ms/step - loss: 0.2244 - accuracy: 0.9161 - val_loss: 0.3186 - val_accuracy: 0.8883
Epoch 00016: early stopping

 

 

* 사용예

  1) Callback 1 : Validation Loss가 향상되는 모델만 저장 

    - save_best_only : True

  2) Callback 2 : 5번 이내에 Loss가 향상되지 않으면 Learning Rate = 기존LR * 0.2

  3) Callback 3 : 10번 이내에 Loss가 향상되지 않으면 더 이상 학습하지 않고 종료

from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

model = create_model()
model.compile(optimizer=Adam(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

# validation loss가 향상되는 모델만 저장.
mcp_cb = ModelCheckpoint(filepath='/kaggle/working/weights.{epoch:02d}-{val_loss:.2f}.hdf5', monitor='val_loss', 
                         save_best_only=True, save_weights_only=True, mode='min', period=1, verbose=0)

# 5번 iteration내에 validation loss가 향상되지 않으면 learning rate을 기존 learning rate * 0.2로 줄임.  
rlr_cb = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, mode='min', verbose=1)
# 10번 iteration내에 validation loss가 향상되지 않으면 더 이상 학습하지 않고 종료
ely_cb = EarlyStopping(monitor='val_loss', patience=10, mode='min', verbose=1)


history = model.fit(x=tr_images, y=tr_oh_labels, batch_size=32, epochs=30, shuffle=True,
                    validation_data=(val_images, val_oh_labels),  
                    callbacks=[mcp_cb, rlr_cb, ely_cb] )

> 결과 (22번 iteration 후 LR 조정, 27번까지 돌고 종료)

Epoch 1/30
2021-11-08 01:31:08.029196: I tensorflow/stream_executor/cuda/cuda_dnn.cc:369] Loaded cuDNN version 8005
1329/1329 [==============================] - 16s 7ms/step - loss: 1.5707 - accuracy: 0.4319 - val_loss: 1.5818 - val_accuracy: 0.4725
Epoch 2/30
1329/1329 [==============================] - 8s 6ms/step - loss: 1.1025 - accuracy: 0.6091 - val_loss: 0.9318 - val_accuracy: 0.6757
Epoch 3/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.9168 - accuracy: 0.6804 - val_loss: 0.8748 - val_accuracy: 0.7053
Epoch 4/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.8169 - accuracy: 0.7155 - val_loss: 0.8552 - val_accuracy: 0.7063
Epoch 5/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.7370 - accuracy: 0.7476 - val_loss: 0.7564 - val_accuracy: 0.7397
Epoch 6/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.6601 - accuracy: 0.7747 - val_loss: 0.6641 - val_accuracy: 0.7764
Epoch 7/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.5948 - accuracy: 0.7965 - val_loss: 0.7745 - val_accuracy: 0.7393
Epoch 8/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.5477 - accuracy: 0.8139 - val_loss: 0.6274 - val_accuracy: 0.7904
Epoch 9/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.4969 - accuracy: 0.8302 - val_loss: 0.5946 - val_accuracy: 0.7999
Epoch 10/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.4542 - accuracy: 0.8427 - val_loss: 0.7064 - val_accuracy: 0.7689
Epoch 11/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.4284 - accuracy: 0.8537 - val_loss: 0.6313 - val_accuracy: 0.7969
Epoch 12/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.3928 - accuracy: 0.8657 - val_loss: 0.6357 - val_accuracy: 0.7984
Epoch 13/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.3600 - accuracy: 0.8757 - val_loss: 0.8772 - val_accuracy: 0.7440
Epoch 14/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.3359 - accuracy: 0.8828 - val_loss: 0.5897 - val_accuracy: 0.8095
Epoch 15/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.3163 - accuracy: 0.8897 - val_loss: 0.5692 - val_accuracy: 0.8196
Epoch 16/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2925 - accuracy: 0.8983 - val_loss: 0.5771 - val_accuracy: 0.8213
Epoch 17/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2770 - accuracy: 0.9027 - val_loss: 0.5220 - val_accuracy: 0.8355
Epoch 18/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2576 - accuracy: 0.9110 - val_loss: 0.8496 - val_accuracy: 0.7651
Epoch 19/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2446 - accuracy: 0.9163 - val_loss: 0.5671 - val_accuracy: 0.8309
Epoch 20/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2308 - accuracy: 0.9202 - val_loss: 0.6052 - val_accuracy: 0.8303
Epoch 21/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2150 - accuracy: 0.9254 - val_loss: 0.6799 - val_accuracy: 0.8145
Epoch 22/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.2024 - accuracy: 0.9299 - val_loss: 0.6664 - val_accuracy: 0.8232

Epoch 00022: ReduceLROnPlateau reducing learning rate to 0.00020000000949949026.
Epoch 23/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.1253 - accuracy: 0.9566 - val_loss: 0.5437 - val_accuracy: 0.8607
Epoch 24/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.0989 - accuracy: 0.9652 - val_loss: 0.5878 - val_accuracy: 0.8515
Epoch 25/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.0834 - accuracy: 0.9700 - val_loss: 0.5921 - val_accuracy: 0.8587
Epoch 26/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.0784 - accuracy: 0.9725 - val_loss: 0.5951 - val_accuracy: 0.8569
Epoch 27/30
1329/1329 [==============================] - 8s 6ms/step - loss: 0.0718 - accuracy: 0.9741 - val_loss: 0.6518 - val_accuracy: 0.8548

Epoch 00027: ReduceLROnPlateau reducing learning rate to 4.0000001899898055e-05.
Epoch 00027: early stopping

+ Recent posts