CrescentCrescent
PostsTagsAbout

> 착한 말로 감자 키우기 (w/ Hugging Face Inference API)

Crescent

다음은 2024년 2월에 진행한 AxT CC (Crash Course) 단기 교육:'누구나 할 수 있는 API 활용 백서 : Open API & ChatGPT편'에서 진행한 실습 내용입니다.

☀️ 전체 코드

p5.js Web Editor

🌱 소개

최종 완성 모습. 왼쪽은 긍정 단어, 오른쪽은 부정 단어를 쳤을 때의 모습이다.
최종 완성 모습. 왼쪽은 긍정 단어, 오른쪽은 부정 단어를 쳤을 때의 모습이다.
  1. 문장을 입력을 받는다. (HTML)
  2. Hugging Face Inference API를 활용, 이 문장이 긍정인지 부정인지 판단한다. (JavaScript)
  3. 결과를 HTML에 표시한다. (JavaScript)
  4. 긍정이면 잎이 자라고 배경이 초록색으로 바뀐다. 그리고 부정이면 잎이 줄어들고 배경이 분홍색으로 바뀐다. (p5.js)

1. 필요한 것

2. 오늘의 Hugging Face AI 모델: [matthewburke/korean_sentiment]

Postman을 사용해 API test한 결과.
Postman을 사용해 API test한 결과.

🥔 만들기

1. 뼈대 만들기

Step A. index.html에서 뼈대 만들기

/index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
    <title>감자 키우기</title>
  </head>
  <body>
    <h2>착한 말로 감자 키우기</h2>
    <form id="textForm">
      <input type="text" id="textInput" placeholder="Enter your sentence" />
      <button type="submit">Submit</button>
    </form>
    <div id="result" style="margin: 0.5rem 0">결과:</div>
    <div id="sketch"></div>
    <script src="sketch.js"></script>
  </body>
</html>
뼈대 만들기 결과: 입력창이 생긴다.

Q. <div id="sketch"></div>는 왜 넣었나요?

A. p5.js가 돌아갈 스케치의 위치를 잡기 위해 만들었습니다.

Q. html 속 요소 마다 id를 붙인 이유가 궁금합니다.

A. JavaScript 사용할 때 특정 요소를 쉽게 찾아가고 조작해야 하는데, id 속성은 문서 전체에서 유일한 식별자를 제공하기 때문입니다.

2. JavaScript를 사용한 API 호출로 문장의 긍정/부정 판단

Step A. script.js 파일 생성 & index.html에 import

/index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
    <title>감자 키우기</title>
  </head>
  <body>
    <h2>착한 말로 감자 키우기</h2>
    <form id="textForm">
      <input type="text" id="textInput" placeholder="Enter your sentence" />
      <button type="submit">Submit</button>
    </form>
    <div id="result" style="margin: 0.5rem 0">결과:</div>
    <div id="sketch"></div>
    <script src="sketch.js"></script>
    <script src="script.js"></script>
  </body>
</html>
script.js를 import

Step B. 입력 버튼 눌렀을 때 콘솔에 호출하도록 꾸리기

/script.js
const textForm = document.getElementById('textForm');
const textInput = document.getElementById('textInput');
 
textForm.onsubmit = function (event) {
  event.preventDefault();
  const text = textInput.value;
 
  console.log("input text:", text)
};
document.getElementById()를 사용

event.preventDefault()? 폼 제출 시, 웹 페이지가 새로고침 되거나 서버로 데이터가 전송되는 것을 방지하는 역할을 합니다.

성공적으로 입력이 된 모습
성공적으로 입력이 된 모습
/script.js
const textForm = document.getElementById('textForm');
const textInput = document.getElementById('textInput');
 
textForm.onsubmit = function (event) {
  event.preventDefault();
  const text = textInput.value;
 
  console.log("input text:", text)
  textInput.value = '';
};
textInput.value=''을 추가하면 입력창이 비워진다.

Step C. Hugging Face Inference API를 호출한다. 그리고 호출 결과는 콘솔에 호출

Hugging Face에서 제공하는 예시를 활용한다
Hugging Face에서 제공하는 예시를 활용한다
/script.js
HUGGINGFACE_API_TOKEN = "INPUT_YOUR_API_TOKEN"
 
const textForm = document.getElementById("textForm");
const textInput = document.getElementById("textInput");
 
textForm.onsubmit = function (event) {
  event.preventDefault();
  const text = textInput.value;
 
  query({ inputs: text, options: { wait_for_model: true } }).then(
    (response) => {
      console.log(text, JSON.stringify(response));
    }
  ).catch((error) => console.error(error));
 
  textInput.value = "";
};
 
async function query(data) {
  try {
    const response = await fetch(
      "https://api-inference.huggingface.co/models/matthewburke/korean_sentiment",
      {
        headers: { Authorization: `Bearer ${HUGGINGFACE_API_TOKEN}` },
        method: "POST",
        body: JSON.stringify(data),
      }
    );
    
    if (!response.ok) 
      throw new Error(`[${response.status}] 서버에서 응답을 받지 못했습니다.`);
 
    return await response.json();
  } catch (error) {
    throw error;
  }
}
Hugging Face Inference API를 호출하는 코드

🤔 async? await? → 쉬운 비동기를 위한 문법

🤔 try...catch문: 코드 실행 중 발생할 수 있는 오류를 처리

🤔 throw: 오류 위로(상위 함수로) 집어 던지기

🤔 Promise 객체 query의 .then() / .catch() 메소드?

'사랑해'를 입력했을 때 나오는 결과
'사랑해'를 입력했을 때 나오는 결과
API KEY를 공백으로 넣었을 때 나오는 결과
API KEY를 공백으로 넣었을 때 나오는 결과

3. 결과를 HTML에 표시한다. (JavaScript)

Stap A. 결과를 정제하는 함수 handleSentimentResponse() 구현

/script.js
HUGGINGFACE_API_TOKEN = "INPUT_YOUR_API_TOKEN"
 
const LABEL_POSITIVE = 'LABEL_1';
const textForm = document.getElementById("textForm");
const textInput = document.getElementById("textInput");
 
textForm.onsubmit = function (event) {
  event.preventDefault();
  const text = textInput.value;
 
  query({ inputs: text, options: { wait_for_model: true } })
    .then((response) => handleSentimentResponse(response, text))
    .catch((error) => console.error(error));
  textInput.value = "";
};
 
// ...
 
function handleSentimentResponse(response, text) {
  const positiveScore = response[0].find(
    (item) => item.label === LABEL_POSITIVE
  ).score;
  const negativeScore = 1 - positiveScore;
  const isPositive = positiveScore > negativeScore;
 
  console.log("text", text);
  console.log("response", response);
  console.log("positiveScore", positiveScore);
  console.log("negativeScore", negativeScore);
  console.log("isPositive", isPositive);
}
fetch한 결과를 정제하는 함수 handleSentimentResponse()구현

결과(response)는 Array로 되어있으며, label로 구분되어 있다.

'사랑해'를 입력했을 때 나오는 결과 (2)
'사랑해'를 입력했을 때 나오는 결과 (2)
'싫어'를 입력했을 때 나오는 결과
'싫어'를 입력했을 때 나오는 결과
'사랑해'를 입력했을 때 나오는 결과 (3)
'사랑해'를 입력했을 때 나오는 결과 (3)

🥰 console.log() 디버깅용 출력이기 때문에 실제로 선보일 때는 지우시는 것이 좋습니다.

Step B. updateUI()를 통해 HTML로 표시하기

/script.js
HUGGINGFACE_API_TOKEN = "INPUT_YOUR_API_TOKEN"
 
const LABEL_POSITIVE = "LABEL_1";
const textForm = document.getElementById("textForm");
const textInput = document.getElementById("textInput");
const resultElement = document.getElementById("result");
 
// ...
 
function handleSentimentResponse(response, text) {
  const positiveScore = response[0].find(
    (item) => item.label === LABEL_POSITIVE
  ).score;
  const negativeScore = 1 - positiveScore;
  const isPositive = positiveScore > negativeScore;
 
  updateUI(text, isPositive, isPositive ? positiveScore : negativeScore);
}
 
function updateUI(text, isPositive, score) {
  const emotion = isPositive ? "긍정" : "부정";
  resultElement.innerText = `[${text}] ${emotion}: ${score}`;
  resultElement.style.color = isPositive ? "green" : "red";
}
updateUI() 함수를 통해 결과를 HTML로 표시하기
'사랑해'와 '미워'를 입력했을 때의 결과
'사랑해'와 '미워'를 입력했을 때의 결과
/index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
    <title>감자 키우기</title>
  </head>
  <body>
    <h2>착한 말로 감자 키우기</h2>
    <form id="textForm">
      <input type="text" id="textInput" placeholder="Enter your sentence" />
      <button type="submit">Submit</button>
    </form>
    <div id="result" style="margin: 0.5rem 0">결과:</div>
    <div id="sketch"></div>
    <script src="sketch.js"></script>
    <script src="script.js"></script>
  </body>
</html>
참고용 index.html

4. 긍정이면 잎이 자라고 배경이 초록색으로 바뀐다. 그리고 부정이면 잎이 줄어들고 배경이 분홍색으로 바뀐다. (p5.js)

Step A. 감자와 싹 그리기

/sketch.js
let seedlingSize = 30;
 
const sketch = (p) => {
  let potatoImg, seedlingImg;
 
  p.preload = () => {
    potatoImg = p.loadImage("potato.png");
    seedlingImg = p.loadImage("seedling.png");
  };
 
  p.setup = () => {
    // id가 sketch인 div에 캔버스를 생성
    p.createCanvas(400, 400).parent('sketch');
    p.imageMode(p.CENTER);
  };
 
  p.draw = () => {
    // 배경에 테두리
    p.noFill();
    p.strokeWeight(4);
    p.rect(0, 0, 400, 400);
 
    // 감자와 새싹 그리기
    p.image(potatoImg, 200, 380, 200, 200);
    p.image(seedlingImg, 200, 320 - seedlingSize / 2, seedlingSize, seedlingSize);
  };
};
 
sketchp5 = new p5(sketch);
400x400 사이즈의 배경에서 감자와 싹 그리기

⚠︎ 이미지 업로드 후 진행 부탁드립니다!

🤔 new p5(sketch)

Q. 왜 하나요?

감자에 싹이나서 잎이나서
감자에 싹이나서 잎이나서

Step B. 원하는 바를 이루기

/script.js
let bgColor;
let seedlingSize = 30;
const GROWTH_SIZE = 40;
 
const sketch = (p) => {
  let potatoImg, seedlingImg;
 
  p.preload = () => {
    potatoImg = p.loadImage("potato.png");
    seedlingImg = p.loadImage("seedling.png");
  };
 
  p.setup = () => {
    // id가 sketch인 div에 캔버스를 생성
    p.createCanvas(400, 400).parent('sketch');
    bgColor = p.color("#ffffff");
    p.imageMode(p.CENTER);
  };
 
  p.draw = () => {
    p.background(bgColor);
 
    // 배경에 테두리
    p.noFill();
    p.strokeWeight(4);
    p.rect(0, 0, 400, 400);
 
    // 감자와 새싹 그리기
    p.image(potatoImg, 200, 380, 200, 200);
    p.image(seedlingImg, 200, 320 - seedlingSize / 2, seedlingSize, seedlingSize);
  };
 
  // this function is called from `script.js`
  p.changePotato = (isPositive, score) => {
    if (isPositive) {
      bgColor = p.color("#03ac13");
      seedlingSize += GROWTH_SIZE;
    } else {
      bgColor = p.color("#ffe1d5");
      seedlingSize = Math.max(30, seedlingSize - GROWTH_SIZE);
    }
    console.log(`${isPositive ? "긍정" : "부정"} 새싹 크기:, ${seedlingSize}`);
 
    p.redraw();
  };
};
 
sketchp5 = new p5(sketch);
긍정과 부정에 따라 싹의 크기가 달라지며 배경색이 달라지는 코드 작성
/script.js
function updateUI(text, isPositive, score) {
  const emotion = isPositive ? "긍정" : "부정";
  resultElement.innerText = `[${text}] ${emotion}: ${score}`;
  resultElement.style.color = isPositive ? "green" : "red";
 
  console.log(resultElement.innerText)
  sketchp5.changePotato(text, isPositive, score);
}
updateUI() 함수를 통해 결과를 HTML로 표시하기
긍정 감자
긍정 감자

🔥 Appendix. 다이나믹하게 즐기기

A. 크기의 변화시키는 변수를 늘리기

부정 감자
부정 감자
/script.js
const sketch = (p) => {
  // this function is called from `script.js`
  p.changePotato = (text, isPositive, score) => {
    if (isPositive) {
      bgColor = p.color("#03ac13");
      seedlingSize += GROWTH_SIZE * score;
    } else {
      bgColor = p.color("#ffe1d5");
      seedlingSize = Math.max(30, seedlingSize - GROWTH_SIZE * score);
    }
    console.log(`새싹 크기: ${seedlingSize}`);
 
    p.redraw();
  };
};
 
sketchp5 = new p5(sketch);
seedlingSize의 변화를 주어 재미를 준다

B. 배경 색을 바꾸기

C. 감자가 아닌 다른 요소를?

🥳 마치며