본문 바로가기
IT - 코딩/AI, 예측모델

꼬맨틀 따라하기(무한맨틀, 사용자 단어 지정가능) with python

by 조기정 2024. 12. 30.

꼬맨틀을 좋아하시는 여러분을 보다가 무한맨틀을 만들어보았어요. 단어는 1~100개만 우선 넣었고 uvcorn(streamlit)으로 실행하면 된답니다.

 

import streamlit as st
import numpy as np
from scipy.spatial.distance import cosine
from sentence_transformers import SentenceTransformer

# -------------------------------------------------------
# 1) 페이지 기본 설정
# -------------------------------------------------------
st.set_page_config(page_title="무한맨틀", layout="centered")
st.title("무한맨틀")


# -------------------------------------------------------
# 2) 모델 로드
#    (원하는 다른 한국어 모델로 교체 가능)
# -------------------------------------------------------
@st.cache_resource
def load_model():
    return SentenceTransformer("snunlp/KR-SBERT-V40K-klueNLI-augSTS") # "snunlp/KR-SBERT-V40K-klueNLI-augSTS" jhgan/ko-sroberta-multitask

model = load_model()

 

여기까지는 모델 불러오는 부분! 여기서 추가 학습을 해서 꼬맨틀의 성능을 높혀볼 수 있어요.

 

 

# -------------------------------------------------------
# 3) 시드 번호 -> 정답 단어 딕셔너리
# -------------------------------------------------------
word_dict = {
    1: "사과",
    2: "컴퓨터",
    3: "하늘",
    4: "숟가락",
    5: "의자",
    6: "바다",
    7: "향수",
    8: "텔레비전",
    9: "소설",
    10: "공원",
    11: "그림",
    12: "안경",
    13: "피아노",
    14: "산",
    15: "축구",
    16: "커피",
    17: "책상",
    18: "지갑",
    19: "빵",
    20: "시계",
    21: "양말",
    22: "자동차",
    23: "식물",
    24: "우산",
    25: "핸드폰",
    26: "문",
    27: "펜",
    28: "구름",
    29: "모자",
    30: "초콜릿",
    31: "버스",
    32: "얼음",
    33: "볼펜",
    34: "샴푸",
    35: "가방",
    36: "신발",
    37: "커튼",
    38: "알람",
    39: "종이",
    40: "비누",
    41: "악기",
    42: "램프",
    43: "토마토",
    44: "물",
    45: "사진",
    46: "비디오",
    47: "구두",
    48: "조각상",
    49: "팔찌",
    50: "빗",
    51: "옷",
    52: "토스트",
    53: "종",
    54: "노트",
    55: "카메라",
    56: "마이크",
    57: "선풍기",
    58: "휴대폰",
    59: "물컵",
    60: "담요",
    61: "헤드폰",
    62: "레몬",
    63: "반지",
    64: "스피커",
    65: "드럼",
    66: "명함",
    67: "안전모",
    68: "연필",
    69: "지구본",
    70: "티셔츠",
    71: "선글라스",
    72: "노트북",
    73: "햄버거",
    74: "모니터",
    75: "라디오",
    76: "손목시계",
    77: "이어폰",
    78: "컵",
    79: "소파",
    80: "타이어",
    81: "전등",
    82: "토끼",
    83: "도서관",
    84: "브러쉬",
    85: "건전지",
    86: "타블렛",
    87: "가위",
    88: "테이프",
    89: "거울",
    90: "기차",
    91: "케이크",
    92: "칠판",
    93: "화분",
    94: "키보드",
    95: "랜턴",
    96: "마우스",
    97: "우표",
    98: "자",
    99: "헬멧",
    100: "전구"
}

 

단어 목록을 넣구, 

 

# -------------------------------------------------------
# 4) 세션 상태 정의
# -------------------------------------------------------
if "seed_number" not in st.session_state:
    st.session_state.seed_number = None
if "answer_word" not in st.session_state:
    st.session_state.answer_word = None
if "attempts" not in st.session_state:
    st.session_state.attempts = []   # [(guess_word, similarity_score), ...]
if "is_correct" not in st.session_state:
    st.session_state.is_correct = False
# "정답 보기" 버튼을 눌렀는지 여부
if "reveal_answer" not in st.session_state:
    st.session_state.reveal_answer = False

 

시드 넘버, 정답 단어, 코사인 유사도를 보여주는 걸 만듭니다. 그리고 정답인지 확인 하는곳, 정 못 맞추겠다면, 정답 보기를 누르면 보이게 합니다

 

# -------------------------------------------------------
# 5) 시드 번호 입력
# -------------------------------------------------------
st.subheader("1. 시드 번호 선택")
seed_number_input = st.number_input(
    label="시드 번호 (1~100)",
    min_value=1,
    max_value=100,
    value=1,
    step=1
)

if st.button("시드 적용"):
    st.session_state.seed_number = seed_number_input
    if seed_number_input in word_dict:
        st.session_state.answer_word = word_dict[seed_number_input]
        st.session_state.attempts = []
        st.session_state.is_correct = False
        st.session_state.reveal_answer = False  # 새 게임 시작 시 정답보기 리셋
        st.success(f"시드번호 {seed_number_input} → 게임시작!")
    else:
        st.session_state.answer_word = None
        st.session_state.attempts = []
        st.session_state.is_correct = False
        st.session_state.reveal_answer = False
        st.warning("잘못된 시드번호입니다.")

시드 번호를 만들고

 

# -------------------------------------------------------
# 6) 포커스 위한 JavaScript (엔터 후 자동 포커스 복귀)
# -------------------------------------------------------
FOCUS_SCRIPT = """
<script>
var input = window.parent.document.querySelector("input[data-testid='stTextInput']");
if (input) {
    input.focus();
}
</script>
"""

 

편의성을 위한 엔터 자동 복귀! 이게 무었이냐면 엔터를 치면 빈칸이 되고, 커서도 그곳에 있어서 계속 답을 쓸 수 있게 만든 것입니다.

 

# -------------------------------------------------------
# 7) 추측 단어 입력 (엔터 제출, 제출 후 자동삭제 & 포커스 유지)
# -------------------------------------------------------
st.subheader("2. 추측 단어 입력")

if not st.session_state.answer_word:
    st.info("먼저 올바른 시드 번호를 적용해주세요.")
else:
    # 폼 사용: 엔터로 제출 + clear_on_submit=True
    with st.form("guess_form", clear_on_submit=True):
        guess = st.text_input(
            "정답 단어를 맞춰보세요 (엔터 제출)",
            placeholder="여기에 입력 후 엔터!"
        )
        submitted = st.form_submit_button("Submit")

    if submitted:
        if st.session_state.is_correct:
            st.warning("이미 정답을 맞추었습니다. 새로운 시드 번호로 시작하세요.")
        else:
            # 정답 체크
            if guess == st.session_state.answer_word:
                st.session_state.is_correct = True
                st.success("정답입니다! 🎉")
            else:
                # 유사도 계산
                embedding_guess = model.encode(guess)
                embedding_answer = model.encode(st.session_state.answer_word)
                similarity_score = 1 - cosine(embedding_guess, embedding_answer)

                # 로그 저장
                st.session_state.attempts.append((guess, similarity_score))
                st.info(f"유사도: **{similarity_score:.4f}**")

    # 렌더링될 때마다 포커스 스크립트 실행 → 입력창 포커스
    st.markdown(FOCUS_SCRIPT, unsafe_allow_html=True)

 

여긴 추측 단어 입력란 입니다. 여기서 추측 단어를 입력하고 정답을 맞추게 합니다.

 

# -------------------------------------------------------
# 8) 추측 로그 (코사인 유사도 높은 순 정렬)
# -------------------------------------------------------
st.subheader("3. 추측 로그")

if st.session_state.attempts:
    # 코사인 유사도 높은 순 (내림차순)
    sorted_attempts = sorted(
        st.session_state.attempts,
        key=lambda x: x[1],
        reverse=True
    )
    for idx, (g, sim) in enumerate(sorted_attempts, start=1):
        st.write(f"**{idx}위** (유사도: {sim:.4f}) → 추측: '{g}'")
else:
    st.write("아직 추측 기록이 없습니다.")

해당 부분은 유사도가 높은 순으로 추측 로그를 남깁니다.

진짜 꼬멘틀은 사람수가 많아서 1순위부터 ~10순위까지 잘 맞추면 순위가 나오는 것으로 보이나, 여기는 그렇게 하기 어려워 이렇게 구성했습니다!

# -------------------------------------------------------
# 9) 정답 보기
# -------------------------------------------------------
st.subheader("4. 정답 보기")

if st.session_state.answer_word:
    if st.session_state.is_correct:
        # 이미 맞혔으면 바로 정답 공개
        st.info(f"현재 정답: {st.session_state.answer_word}")
    else:
        # "정답 보기" 버튼 표시
        if st.button("정답 보기"):
            st.session_state.reveal_answer = True

        # reveal_answer가 True면 정답 공개
        if st.session_state.reveal_answer:
            st.info(f"현재 정답: {st.session_state.answer_word}")

말그대로 정답 세션입니다.

 

 

아래는 실제 실행시 이렇게 보인답니다.

http://121.78.147.172:7195/