본문 바로가기

컴퓨터/Backend

[졸업 프로젝트]_3. 알고리즘 설계와 구현(2차 알고리즘)_+Teachable Machine 활용법

유기견을 품종 분류를 위한 모델로 

구글의 머신러닝 라이브러리인 Teachable Machine을 활용하기로 하였고,

따라서 사용법과 코드 로직에 대한 이해가 필요했다.

(Teacheble Machine을 줄여서 TM이라고 하겠다.)

 

 우선, TM의 활용 방법을 익히기 위해

조코딩님의 영상을 참고했다.

https://youtu.be/USQGTW34lO8 

 

우리 프로젝트에서 Teachable Machine을 어떻게 활용했는지 활용법을 간략히 정리하자면 다음과 같다.


  1. 이미지를 업로드하거나 웹캠을 사용하여 Teachable Machine에 이미지를 학습시킨다.
  2. 학습시킨 모델을 export한다. (tensorflow, tensorflow.js, tensorflow Lite 3가지 방법)
  3. 우리가 사용할 모델을 다운로드하면 3가지 파일이 받아진다.
  4. javascript로 된 TM예시 코드를 사용하기 위해 index.html파일을 만들어준 후 javascript 코드를 copy한다.
  5. 추출한 모델을 테스트 하기 위해 정적 사이트를 배포할 수 있게 해주는 netlify를 이용한다.
  6. 처음 추출한 TM모델은 웹캠 방식으로 작동하기 때문에 이미지 업로드 방식을 사용할 수 있게 코드를 변형해준다.
  7. 학습된 TM 모델 소스 코드에 2차 알고리즘을 추가하여 테스트한다.
  8. 화면이 출력되는 부분을 변경한다.

학습된  TM 모델을 분석하기 위해 우선 많은 수의 강아지 사진 데이터(믹스견과 품종견 포함)가 필요했다.

그래서 kaggle을 비롯하여 여러 사이트로부터 크롤링을 하여 이미지 데이터를 수집하였고, 

이 데이터를 가지고 Teachable Machine을 학습시켰다.

 

이미지 데이터의 테스트가 필요했기 때문에 몇 번의 학습 끝에

최종적으로 사용할 TM모델을 추출하였고,

나는 그 코드를 분석하여 더욱 정교하게 품종을 가려낼 알고리즘을 추가하였다.

 

추가될 2차 알고리즘 로직은 다음과 같이 정리하였다.


      1. 사용자가 업로드한 이미지에 대해 추출된 TM 모델의 결과값들을 {key:value} 형태로 저장하며, key에는 품종이 value에는 가능성 값을 넣어준다. (*가능성 값이란 사용자가 업로드한 이미지로부터 해당 품종이 얼마만큼 일치하는지에 대한 값으로 퍼센테이지로 나타낸다.)
      2. 추출된 품종의 가능성 값을 내림차순으로 정렬한다.
      3. 가능성이 0인 품종 목록들은 삭제한다. (Teachable Machine을 처음 확인해보면 가능성이 0인(해당되지 않는) 품종들도 함께 화면에 출력되기 때문이다. )
      4. 1차 알고리즘과 연결하여 DB 쿼리로 도출된 품종 목록 값과 TM 모델의 결과값을 비교하여 같은 품종을 추출한다.
      5. 같은 품종이 3개 이상일 경우 가장 상위의 3개 품종 추출 
      6. 같은 품종이 3개 미만일 경우

                       6-1. 같은 품종이 0: TM 모델의 상위 품종 3개를 그대로 추출하여 출력

                       6-2. 같은 품종이 1: C + TM 모델의 상위 품종 2개를 추출하여 출력

                       6-3. 같은 품종이 2: C + TM 모델의 상위 품종 1개를 추출하여 출력      

* TM 모델의 결과 값이 3개 이하인 경우를 고려하여 “undefined” 예외 처리 코드를 작성

 


<2차 알고리즘 코드>

//여기서부터 수정된 코드입니다.

            //dict는 업로드 된 이미지에 대해 추출된 TM 모델 결과값들을 key(품종 목록): value(퍼센트) 형태로 저장한 객체입니다.
            var dict = {};
            for (let i = 0; i < maxPredictions; i++) {
                dict[prediction[i].className] = prediction[i].probability.toFixed(2);

            }
            console.log(dict);

            //가능성이 높은 품종 목록을 추출하기 위해 퍼센트 값에 따라 내림차순 정렬을 하는 코드입니다. 
            //'sortable'이라는 배열을 생성하여 값들을 배열로 옮긴 후 내림차순 정렬을 하였습니다. (객체 -> 배열)
            var sortable = [];
            for (var i in dict) {
                sortable.push([i, dict[i]]);
            }

            sortable.sort(function (a, b) {
                return b[1] - a[1];
            });
           
          

            //dictObject라는 객체는 위 내림차순된 배열 값을 다시 key:value 값으로 저장하기 위한 객체입니다. (배열-> 객체)
            var dictObject = {};
            for (let i = 0; i < sortable.length; i++) {
                var first = sortable[i];
                dictObject[first[0]] = first[1];
                
            }

            console.log(dictObject);
           
            //dictObject 객체에서 퍼센테이지가 0인 품종 목록들을 삭제해주는 부분입니다.
            for (var zero in dictObject) {
                if (dictObject[zero] == 0.00) {
                    delete dictObject[zero];
                }
            }
            console.log(dictObject);

            //테스트 객체 (db에서 넘겨받은 값으로 테스트를 위해 임의로 값을 3개정도 설정하였습니다.)
            var a = {};
            a['말티즈'] = '';
            a['토이 푸들'] = '';
            a['치와와'] = '';



            var later = {}; // 추후 데이터베이스에서 추출한 값과 이름이 같은 것들만 저장할 부분
            // db에서 전달받은 품종 목록과 dictObject의 각 요소를 비교하여 품종 목록이 같은 요소만 추출하여 새로운 객체 later에 저장하는 부분입니다.    
            for (let i = 0; i < Object.keys(a).length; i++) {
                    const curr = Object.keys(a)[i];
                    for (let j = 0; j < Object.keys(dictObject).length; j++) {
                        if (curr == Object.keys(dictObject)[j]) {
                            later[Object.keys(dictObject)[j]] = dictObject[Object.keys(dictObject)[j]];
                        }
                    }
               }
            console.log(later);
            
            //추출된 later을 다시 내림차순 정렬하는 부분입니다. 로직은 위의 배열-객체 전환 로직과 동일합니다.
            var sortable2 = [];

            for (var keys in later) {
                    sortable2.push([keys, later[keys]]);
             }


            sortable2.sort(function (a, b) {
                    return b[1] - a[1];
              });
            //내림차순 정렬된 객체 c는 최종적으로 출력에 사용할 객체입니다.
            var c = {};
            for (let i = 0; i < sortable2.length; i++) {
                var sec = sortable2[i];
                c[sec[0]] = sec[1];
            }
          
            console.log(c)

            // 여기서부터 출력과 관련된 코드입니다.
            
            //객체 c의 요소가 3개 이상이라면 가장 처음 3개 요소를 추출합니다.
            if (Object.keys(c).length >= 3) {
                    for (let i = 0; i < 3; i++) {
                        const dictc = Object.keys(c)[i] + ":" + dictObject[Object.keys(dictObject)[i]];
                        labelContainer.childNodes[i].innerHTML = dictc;
                    }
             }

            //객체 c의 요소가 3개 이하인 부분으로 3가지로 나눠집니다. 
            else {
                if (Object.keys(c).length == 0) {                               //c의 요소가 0개인 경우 : tm 모델에서 가장 높은 값 3개를 가져와 그대로 추출합니다.        
                    var dic = [];
                    for (var i = 0; i < 3; i++) {
                        dic.push(Object.keys(dictObject)[i]);
                    }
                    for (var j in dic) {
                        if(typeof(dic[j]) !== "undefined")
                        labelContainer.childNodes[j].innerHTML = dic[j];
                    }

                    }
                    else if (Object.keys(c).length == 1) {                              //c의 요소가 1개인 경우 : tm모델 품종 목록에서 c와 동일한 요소를 삭제한 후 가장 상위 요소 2개를 추가로 추출합니다.
                        var cfirst = c[0];
                        delete dictObject[cfirst];
                        
                        for (let i = 0; i < 2; i++) {
                            c[Object.keys(dictObject)[i]] = dictObject[Object.keys(dictObject)[i]];

                        }
                        //예외처리
                        for (var def in c) {    
                            if (typeof (c[def]) === "undefined") {
                                delete c[def];
                            }
                        }
                       
                        for (let i = 0; i < Object.keys(c).length; i++) {                 
                            
                            const dictc1 = Object.keys(c)[i];
                            labelContainer.childNodes[i].innerHTML = dictc1;
                        }

                    }

                    else {                                                                // c의 요소가 2개인 경우: 마찬가지로 tm 모델 품종 목록에서 c와 동일한 요소들을 삭제한 후 가장 상위 요소 1개를 추출합니다.
                        var cfirst = c[0];
                        var csecond = c[1];
                        delete dictObject[cfirst];
                        delete dictObject[csecond];

                        c[Object.keys(dictObject)[0]] = dictObject[Object.keys(dictObject)[0]];
                        //예외처리
                        for (var def in c) {
                            if (typeof (c[def]) === "undefined") {
                                delete c[def];
                            }
                        }
                        
                        for (let i = 0; i < Object.keys(c).length; i++) {
                            
                            const dictc2 = Object.keys(c)[i];
                                labelContainer.childNodes[i].innerHTML = dictc2;
                           
                        }

                    }
                }
            }
        
   
    </script>

TM 모델에 연결한 2차 알고리즘 코드이다.

생각보다 경우의 수가 많아서 여러 차례 수정하였다.

 

알고리즘을 추가한 뒤 서버에 연결하기전 netlify로 테스트한 결과는 다음과 같다.


 

ex) 토이 푸들 사진을 업로드하였을 경우

개발자 도구를 통해 추출되는 품종 목록과 결과값을 확인하였고,

DB 연동 전에는 임의로 테스트 객체를 설정하여 테스트를 진행하였다.

 

 

 

* 본 포스팅은 개인용으로 작성한 것이므로 무단 복제나 펌을 금지합니다.

728x90