개요 Link to heading

사이드 프로젝트를 진행하면서, 임의의 문장 또는 단락에 대해 어떤 언어로 작성된건지 확인해야할 필요가 생겨서 방법을 찾아보았고, 해당 내용을 정리해보았다.

Apache TikaLanguageDetector 를 사용해보고자 한다.

시작하기 Link to heading

dependencies {
    implementation(platform("org.apache.tika:tika-bom:2.9.1"))
    implementation("org.apache.tika:tika-core")
    implementation("org.apache.tika:tika-parsers-standard-package")
    implementation("org.apache.tika:tika-langdetect-optimaize")
}

언어 감지하기 Link to heading

먼저 languageDetector 를 아래와 같이 설정하면 된다.

fun detectLanguage(text: String): String {
    val languageDetector = LanguageDetector.getDefaultLanguageDetector().loadModels()
    return languageDetector.detect(text).language
}
class LanguageServiceTest {
    private val sut = LanguageService()

    @Test
    fun detectLanguageTest() {
        assertEquals(sut.detectLanguage("나는 배고프다."), "ko")
        assertEquals(sut.detectLanguage("If it weren't for you, it would be really hard."), "en")
    }
}

실제 리턴해주는 값은 아래와 같다.

@Test
fun detectLanguageTest() {
    val languageDetector = LanguageDetector.getDefaultLanguageDetector().loadModels()

    println(
        languageDetector.detectAll(
            """
                나는 정말 힘들었을 거야 라는 문장은 다음과 같이 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
            """.trimIndent()
        )
    )
    /**
     * [ko: MEDIUM (0.571428), en: MEDIUM (0.428569)]
     */
}

특정 언어만 Detector 로 설정하기 Link to heading

val languageDetector = LanguageDetector.getDefaultLanguageDetector()
    .loadModels(setOf("en", "ko"))

loadModelsISO 639-1 code 를 Set 으로 넣어주면 된다.

그 밖의 설정들 Link to heading

ShortText Link to heading

val languageDetector = LanguageDetector.getDefaultLanguageDetector()
    .setShortText(true)

MixedLanguages Link to heading

val languageDetector = LanguageDetector.getDefaultLanguageDetector()
    .setMixedLanguages(true)

유의해야 할 점 Link to heading

생각보다 부정확할 수 있다.

예를 들어 나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다. 라는 문장을 detect 할 경우, 아래와 같이 잘못된 언어가 우선되는 경우가 있다.

@Test
fun detectLanguageTest() {
    val languageDetector = LanguageDetector.getDefaultLanguageDetector().loadModels()

    println(
        languageDetector.detectAll(
            """
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
            """.trimIndent()
        )
    )
    /**
     * [en: MEDIUM (0.857138), ko: MEDIUM (0.142857)]
     */
}

이 경우 문장이 짧아서 그렇지 않나라고 생각할 수 있어서 다음과 같이 시도해보면, 아래와 같이 바뀌기는 한다.

@Test
fun detectLanguageTest() {
    val languageDetector = LanguageDetector.getDefaultLanguageDetector().loadModels()

    println(
        languageDetector.detectAll(
            """
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
                나는 정말 힘들었을 거야는 더 자연스럽게 'I would have really struggled'로 번역될 수 있습니다.
            """.trimIndent()
        )
    )
    /**
     * [ko: MEDIUM (0.571427), en: MEDIUM (0.428570)]
     */
}

하지만, 저 기준을 어떻게 잡아야 할까? 상당히 문장에 의존적인 변수가 될 것으로 보인다.

따라서 해당 방법을 적용할 때 위의 내용을 유의하면 좋을 것 같다.

결론 Link to heading

이 라이브러리의 동작 방식은 n-gram 을 통해 언어 프로필을 뽑아낸 뒤에 상대 빈도 비교를 통해 추론해내는 방식이다.

하지만, 언어란 워낙 다양하게 쓰이기 때문에, 라이브러리만으로 정확하게 판단하기란 쉽지 않다는 것을 알게 되었다.

다만, 커다란 글 타래의 경우 보다 정확하게 판단 가능할 것으로 보인다.

References Link to heading