GPT API나 개인 인스턴스 사용 시, 질문과 다른 언어로 답변하는 문제가 종종 발생한다. 특히 파일 검색이나 인터넷 검색을 사용할 때 빈번하게 나타난다.
프롬프트 개선으로 이를 해결하고자 하였지만, 생각보다 문제가 해결되지 않아 코드 레벨에서 이를 해결하고자 한다.
경험상, 질문의 마지막에 언어를 명시하면(예: "한국어로 답변해줘") 문제는 대부분 해결된다. 이를 자동화하기 위해 질문의 언어를 감지해 적절한 답변 요청 문구를 추가하는 방식으로 해결하고자 한다.
사용자의 질문 언어를 감지해 해당 언어로 답변을 요청하는 Suffix를 자동으로 추가하는 방식으로 언어 오류를 최소화하고자 한다.
구현은 너무나 간단하다. 브루트포스로 문자열을 순회하며 regex(또는 아스키 코드)로 언어의 특성을 파악하여 해시맵에 가중치를 기록하는 선형적으로 증가하는 O(n)
방식이다.
type Language = 'en' | 'ko' | 'ja';
export const Prompt = {
getLanguage(message: string): Language {
const weight: Record<Language, number> = {
en: 0,
ko: 0,
ja: 0,
};
for (const char of message) {
if (/[a-zA-Z]/.test(char)) {
weight.en++;
} else if (/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(char)) {
weight.ko++;
} else if (/[\u3040-\u30ff\u4e00-\u9faf]/.test(char)) {
weight.ja++;
}
}
const detectedLanguage = (Object.keys(weight) as Language[]).reduce(
(maxLang, lang) => (weight[lang] > weight[maxLang] ? lang : maxLang),
'en' as Language
);
return detectedLanguage;
},
addSuffix(message: string) {
const languageSuffixMap = {
ko: '한국어로 답변해주세요.',
en: 'Please answer in English.',
ja: '日本語で回答してください.',
};
const languageKey = Prompt.getLanguage(message);
return `${message} ${languageSuffixMap[languageKey]}`;
},
};
평균적으로 들어오는 메시지는 100~150자 사이로, 띄어쓰기를 제외(한국 사용자 기준) 모든 문자를 검사하는 대신, 메시지의 일부만 샘플링해도 충분히 정확한 언어 감지가 가능하다. 이를 통해 성능을 개선하고자 한다.
메시지 길이에 간격 샘플링을 진행하여 k번째 문자마다 샘플링하여 성능과 정확성의 균형을 맞춰 시간복잡도를
O(n)
에서O(n/k)
. 로 변경하고자 한다.
// k번째 문자만 검토하도록 수정
for (let i = 0; i < message.length; i += k) {
const char = message[i];
if (/[a-zA-Z]/.test(char)) {
weight.en++;
} else if (/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(char)) {
weight.ko++;
} else if (/[\u3040-\u30ff\u4e00-\u9faf]/.test(char)) {
weight.ja++;
}
}
적절한
k
?띄어쓰기로 나눠지는 단위를
청크
라고 가정하자.(일어는 띄어쓰기가 없으니 제외한다.)
한국어는 보통 조사와 어미가 하나의 단어에 추가로 결합된다. 하나의 청크에는 본래의 단어(2~4자가 70~80%를 차지한다)와 조사나 어미(1~2자)가 결합되어, 평균적으로 한 청크는 3~5자 정도일 것으로 예상하였다.
영어는 조사나 어미 대신 전치사나 대명사 같은 별도의 단어들이 따로 표기된다. 즉, 하나의 청크는 기본적으로 단어 자체만으로 구성된다. 단어의 평균 길이가 약 4.7~5.4자이므로, 하나의 청크는 보통 4~5자로 구성된다.
일본어는 조사나 접속사 같은 요소들이 단어에 결합되지 않고 독립적으로 쓰이는 경우가 대부분으로 보인다. 다만, 문법상의 특성(동사 변화나 조사의 영향)으로 청크가 길어질 수 있다. 하나의 단어(한자 포함)는 평균 4~5자 정도이며, 여기에 조사나 어미가 추가되어, 하나의 청크는 6~8자 정도로 추산한다.
k
값 산출모집단에서 적절한 표본 크기를 구하기 위해 통계학에서는 신뢰도와 오차범위를 고려한 표본 추출 방식을 사용한다.
실데이터(평균 메시지 길이)의 크기를 기반으로 적절한 k
값을 구하기 위해 신뢰 구간을 적용한다.
n = (Z^2 * p * (1-p)) / e^2
// n: 신뢰 임계치를 넘기기 위해, 최소로 샘플링 해야하는 표본 문자열의 크기
// Z: 신뢰도에 따른 Z값 (99% 신뢰도에서 Z = 2.576)
// p: 모집단의 비율 (언어가 잘못 분류될 확률을 50%로 가정. 보수적 계산)
// e: 허용 오차 (일반적으로 5%)
이를 통해 필요한 표본 크기를 계산하자. 한국 사용자 기준으로 평균적인 질문 메시지의 길이는 100자에서 200자 사이였다. 여기서는 높은 신뢰도를 위해 모집단의 평균을 100자로 계산한다.
99% 신뢰도를 넘기기 위해 필요한 표본 크기(n)는 약 87(87%)
자를 샘플링해야 충분히 높은 신뢰도로 언어를 감지할 수 있음을 의미한다.
k
값은 약 1.15
로 이는 사실상 거의 모든 문자마다 샘플링하는 방식에 가깝다.
개선되는 시간복잡도는 굉장히 미미하지만, 언어가 잘못 매핑되어 UX를 해치는 기대값을 생각했을 때, 득보다는 실이 크다고 판단되었다.
문자열이 굉장히 긴 경우, 위에서 언급한대로 표본 추출을 통해 시간복잡도를 개선하거나, 나아가 사용하는 언어별 아스키코드를 360도로 분배하여 원점으로부터 기울기 및 좌표를 계산하는 방식으로 최적화가 가능해보인다. 시간복잡도 개선을 위한 다양한 아이디어 적용이 가능해보인다.