형태소 분석기 은전한닢이 스칼라용이 있어서 시범삼아 돌려보았다. 

실행방법은 너무나 간단하다. 


참고 페이지

  • https://bitbucket.org/eunjeon/seunjeon


build.sbt에 다음 내용 추가

libraryDependencies += "org.bitbucket.eunjeon" %% "seunjeon" % "1.1.1"



그리고 다음 코드 실행해봄

object Test extends App {
  import org.bitbucket.eunjeon.seunjeon.Analyzer

  // 형태소 분석
  println( "@@ Step 1")
  Analyzer.parse("아버지가방에들어가신다").foreach(println)

  // 어절 분석
  println( "@@ Step 2")
  Analyzer.parseEojeol("아버지가방에들어가신다.").foreach(println)
  // or
  println( "@@ Step 3")
  Analyzer.parseEojeol(Analyzer.parse("아버지가방에들어가신다.")).foreach(println)

  /**
   * 사용자 사전 추가
   * surface,cost
   *   surface: 단어
   *   cost: 단어 출연 비용. 작을수록 출연할 확률이 높다.
   */
  println( "@@ Step 4")
  Analyzer.setUserDict(Seq("덕후", "버카충,-100", "낄끼빠빠").toIterator)
  Analyzer.parse("덕후냄새가 난다.").foreach(println)

  // 활용어 원형
  println( "@@ Step 5")
  Analyzer.parse("빨라짐").flatMap(_.deInflect()).foreach(println)

  // 복합명사 분해
  println( "@@ Step 6")
  Analyzer.parse("삼성전자").flatMap(_.deCompound()).foreach(println)
}



실행결과 (가독성을 위해 일부 메시지 제거)

[info] Running Test
@@ Step 1
LNode(Morpheme(아버지,1784,3536,2818,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135)
LNode(Morpheme(가,490,1044,1501,WrappedArray(JKS, *, F, 가, *, *, *, *),COMMON,WrappedArray(J)),3,4,-738)
LNode(Morpheme(방,1784,3537,2975,WrappedArray(NNG, *, T, 방, *, *, *, *),COMMON,WrappedArray(N)),4,5,660)
LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,203)
LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,583)
LNode(Morpheme(신다,5,4,3583,WrappedArray(EP+EC, *, F, 신다, Inflect, EP, EC, 시/EP/*+ㄴ다/EC/*),INFLECT,WrappedArray(EP, E)),9,11,-1256)

@@ Step 2
Eojeol(MutableList(LNode(Morpheme(아버지,1784,3536,2818,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135), LNode(Morpheme(가,490,1044,1501,WrappedArray(JKS, *, F, 가, *, *, *, *),COMMON,WrappedArray(J)),3,4,-738)))
Eojeol(MutableList(LNode(Morpheme(방,1784,3537,2975,WrappedArray(NNG, *, T, 방, *, *, *, *),COMMON,WrappedArray(N)),4,5,660), LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,203)))
Eojeol(MutableList(LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,583), LNode(Morpheme(신다,5,6,3600,WrappedArray(EP+EF, *, F, 신다, Inflect, EP, EF, 시/EP/*+ᆫ다/EF/*),INFLECT,WrappedArray(EP, E)),9,11,-1256)))
Eojeol(MutableList(LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),11,12,325)))

@@ Step 3
Eojeol(MutableList(LNode(Morpheme(아버지,1784,3536,2818,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135), LNode(Morpheme(가,490,1044,1501,WrappedArray(JKS, *, F, 가, *, *, *, *),COMMON,WrappedArray(J)),3,4,-738)))
Eojeol(MutableList(LNode(Morpheme(방,1784,3537,2975,WrappedArray(NNG, *, T, 방, *, *, *, *),COMMON,WrappedArray(N)),4,5,660), LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,203)))
Eojeol(MutableList(LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,583), LNode(Morpheme(신다,5,6,3600,WrappedArray(EP+EF, *, F, 신다, Inflect, EP, EF, 시/EP/*+ᆫ다/EF/*),INFLECT,WrappedArray(EP, E)),9,11,-1256)))
Eojeol(MutableList(LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),11,12,325)))

@@ Step 4
LNode(Morpheme(덕후,1784,3535,800,WrappedArray(NNG, *, F, 덕후, *, *, *, *),COMMON,WrappedArray(N)),0,2,-1135)
LNode(Morpheme(냄새,1784,3536,2123,WrappedArray(NNG, *, F, 냄새, *, *, *, *),COMMON,WrappedArray(N)),2,4,-139)
LNode(Morpheme(가,490,1044,1501,WrappedArray(JKS, *, F, 가, *, *, *, *),COMMON,WrappedArray(J)),4,5,-437)
LNode(Morpheme(난다,2421,6,1277,WrappedArray(VV+EF, *, F, 난다, Inflect, VV, EF, 나/VV/*+ᆫ다/EF/*),INFLECT,WrappedArray(V, E)),6,8,348)
LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),8,9,-394)

@@ Step 5
LNode(Morpheme(빠르,-1,-1,0,WrappedArray(VA),COMMON,WrappedArray(V)),0,2,-1092)
LNode(Morpheme(지,-1,-1,0,WrappedArray(VX),COMMON,WrappedArray(V)),2,3,-1092)
LNode(Morpheme(ᄆ,-1,-1,0,WrappedArray(ETN),COMMON,WrappedArray(E)),2,3,-1092)

@@ Step 6
LNode(Morpheme(삼성,-1,-1,0,WrappedArray(NNG),COMMON,WrappedArray(N)),0,2,-2008)
LNode(Morpheme(전자,-1,-1,0,WrappedArray(NNG),COMMON,WrappedArray(N)),2,4,-2008)




내용 분석을 위한 사전 참고 사항

품사 태그 테이블 - https://docs.google.com/spreadsheets/d/1-9blXKjtjeKZqsf4NzHeYJCrr49-nXeRF6D80udfcwY/edit#gid=589544265

실질의미유무
대분류(5언 + 기타)
세종 품사 태그mecab-ko-dic 품사 태그
태그설명태그설명
실질형태소
체언
NNG일반 명사NNG일반 명사
NNP고유 명사NNP고유 명사
NNB
의존 명사
NNB의존 명사
NNBC단위를 나타내는 명사
NR수사NR수사
NP대명사NP대명사
용언
VV동사VV동사
VA형용사VA형용사
VX보조 용언VX보조 용언
VCP긍정 지정사VCP긍정 지정사
VCN부정 지정사VCN부정 지정사
수식언
MM관형사MM관형사
MAG일반 부사MAG일반 부사
MAJ접속 부사MAJ접속 부사
독립언IC감탄사IC감탄사
형식형태소
관계언
JKS주격 조사JKS주격 조사
JKC보격 조사JKC보격 조사
JKG관형격 조사JKG관형격 조사
JKO목적격 조사JKO목적격 조사
JKB부사격 조사JKB부사격 조사
JKV호격 조사JKV호격 조사
JKQ인용격 조사JKQ인용격 조사
JX보조사JX보조사
JC접속 조사JC접속 조사
선어말 어미EP선어말 어미EP선어말 어미
어말 어미
EF종결 어미EF종결 어미
EC연결 어미EC연결 어미
ETN명사형 전성 어미ETN명사형 전성 어미
ETM관형형 전성 어미 ETM관형형 전성 어미
접두사XPN체언 접두사XPN체언 접두사
접미사
XSN명사 파생 접미사XSN명사 파생 접미사
XSV동사 파생 접미사XSV동사 파생 접미사
XSA형용사 파생 접미사XSA형용사 파생 접미사
어근XR어근XR어근
부호
SF마침표, 물음표, 느낌표SF마침표, 물음표, 느낌표
SE줄임표SE줄임표 …
SS
따옴표,괄호표,줄표
SSO여는 괄호 (, [
SSC닫는 괄호 ), ]
SP쉼표,가운뎃점,콜론,빗금SC구분자 , · / :
SO붙임표(물결,숨김,빠짐)
SY
SW기타기호 (논리수학기호,화폐기호)
한글 이외
SL외국어SL외국어
SH한자SH한자
SN숫자SN숫자


내용이 너무 많아서 다사 sbt console에서 하나씩 돌려봄

$ sbt console

[info] Starting scala interpreter...

[info]

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_91).

Type in expressions for evaluation. Or try :help.


scala> import org.bitbucket.eunjeon.seunjeon.Analyzer

import org.bitbucket.eunjeon.seunjeon.Analyzer


scala> Analyzer.parse("아버지가방에들어가신다.").foreach(println)

LNode(Morpheme(아버지,1784,3536,2818,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135)

LNode(Morpheme(가,490,1044,1501,WrappedArray(JKS, *, F, 가, *, *, *, *),COMMON,WrappedArray(J)),3,4,-738)

LNode(Morpheme(방,1784,3537,2975,WrappedArray(NNG, *, T, 방, *, *, *, *),COMMON,WrappedArray(N)),4,5,660)

LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,203)

LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,583)

LNode(Morpheme(신다,5,6,3600,WrappedArray(EP+EF, *, F, 신다, Inflect, EP, EF, 시/EP/*+ᆫ다/EF/*),INFLECT,WrappedArray(EP, E)),9,11,-1256)

LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),11,12,325)


scala>

 


결과를 보니 LNode, Morpheme라는 클래스들이 많이 보인다. 이게 뭔지 알아보자.


/**

  * Lattice 노드

  * @param morpheme   Morpheme

  * @param startPos  시작 offset

  * @param endPos   끝 offset

  * @param accumulatedCost  누적비용

  */

case class LNode(morpheme:Morpheme,

                 var startPos:Int,

                 var endPos:Int,

                 var accumulatedCost:Int = Int.MaxValue) {

 

/**

  * 형태소

  * @param surface  표현층

  * @param leftId   좌문맥ID

  * @param rightId  우문맥ID

  * @param cost     Term 비용

  * @param feature  feature

  * @param poses    품사 

  */

@SerialVersionUID(1000L)

case class Morpheme(var surface:String,

                    var leftId:Short,

                    var rightId:Short,

                    var cost:Int,

                    var feature:mutable.WrappedArray[String],

                    var mType:MorphemeType,

                    var poses:mutable.WrappedArray[Pos]) extends Serializable {




샘플

// Lattice 노드 
LNode(

    // 형태소
    Morpheme(
        아버지,         // surface - 표현층
        1784,           // leftId - 좌문맥Id
        3536,           // rightId - 우문맥Id
        2818,           // cost - 비용
        WrappedArray(NNG, *, F, 아버지, *, *, *, *), // feature
        COMMON,         // morpheme type
        WrappedArray(N) // poses - 품사
    ),
    0,     // 시작 offset
    3,     // 끝 offset
    -1135  // 누적비용
)


위 예제를 변형해서 표현층만 추출해보자. 

scala> Analyzer.parse("아버지가방에들어가신다.").toList.foreach( node => println(node.morpheme.surface ))

아버지



들어가
신다
.

scala>



사용자 사전을 추가해보자.

scala> Analyzer.setUserDict(Seq("아버지", "가방").toIterator)
scala> Analyzer.parse("아버지가방에들어가신다.").foreach(println)

LNode(Morpheme(아버지,1784,3535,700,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135)
LNode(Morpheme(가방,1784,3535,800,WrappedArray(NNG, *, T, 가방, *, *, *, *),COMMON,WrappedArray(N)),3,5,-239)
LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,-3709)
LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,-3329)
LNode(Morpheme(신다,5,6,3600,WrappedArray(EP+EF, *, F, 신다, Inflect, EP, EF, 시/EP/*+ᆫ다/EF/*),INFLECT,WrappedArray(EP, E)),9,11,-5168)
LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),11,12,-3587)

scala> 


어절분석

scala> Analyzer.parseEojeol("아버지가방에들어가신다.").foreach(println)

Eojeol(
  MutableList(
    LNode(Morpheme(아버지,1784,3535,700,WrappedArray(NNG, *, F, 아버지, *, *, *, *),COMMON,WrappedArray(N)),0,3,-1135)
  )
)
Eojeol(
  MutableList(
    LNode(Morpheme(가방,1784,3535,800,WrappedArray(NNG, *, T, 가방, *, *, *, *),COMMON,WrappedArray(N)),3,5,-239),
    LNode(Morpheme(에,356,307,1248,WrappedArray(JKB, *, F, 에, *, *, *, *),COMMON,WrappedArray(J)),5,6,-3709))
)
Eojeol(
  MutableList(
    LNode(Morpheme(들어가,2421,3574,1648,WrappedArray(VV, *, F, 들어가, *, *, *, *),COMMON,WrappedArray(V)),6,9,-3329),
    LNode(Morpheme(신다,5,6,3600,WrappedArray(EP+EF, *, F, 신다, Inflect, EP, EF, 시/EP/*+ᆫ다/EF/*),INFLECT,WrappedArray(EP, E)),9,11,-5168))
)
Eojeol(
  MutableList(
    LNode(Morpheme(.,1794,3555,3559,WrappedArray(SF, *, *, *, *, *, *, *),COMMON,WrappedArray(S)),11,12,-3587))
)

scala>

 


복합명사 분해

scala> Analyzer.parse("삼성전자").flatMap(_.deCompound()).foreach(println)

LNode(Morpheme(삼성,-1,-1,0,WrappedArray(NNG),COMMON,WrappedArray(N)),0,2,-2008)

LNode(Morpheme(전자,-1,-1,0,WrappedArray(NNG),COMMON,WrappedArray(N)),2,4,-2008)

 






[error] (run-main-0) java.nio.charset.MalformedInputException: Input length = 1

java.nio.charset.MalformedInputException: Input length = 1

        at java.nio.charset.CoderResult.throwException(CoderResult.java:281)

        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)

        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)

        at java.io.InputStreamReader.read(InputStreamReader.java:184)

        at java.io.BufferedReader.fill(BufferedReader.java:161)

        at java.io.BufferedReader.readLine(BufferedReader.java:324)

        at java.io.BufferedReader.readLine(BufferedReader.java:389)



위와 같은 에러가 발생했다면 다음 설정을 .bashrc에 추가해주자.


export JAVA_OPTS="-Dfile.encoding=UTF-8"


디렉토리 생성


mkdir -pv ~/.vim/ftdetect
mkdir -pv ~/.vim/indent
mkdir -pv ~/.vim/syntax


파일 복사


wget --no-check-certificate https://lampsvn.epfl.ch/trac/scala/export/18260/scala-tool-support/trunk/src/vim/ftdetect/scala.vim -O ~/.vim/ftdetect/scala.vim
wget --no-check-certificate https://lampsvn.epfl.ch/trac/scala/export/18260/scala-tool-support/trunk/src/vim/indent/scala.vim -O ~/.vim/indent/scala.vim
wget --no-check-certificate https://lampsvn.epfl.ch/trac/scala/export/18260/scala-tool-support/trunk/src/vim/syntax/scala.vim -O ~/.vim/syntax/scala.vim


참고 문서: https://blog.outsider.ne.kr/523







인코딩이 문제일 수 있다.

eclipse.ini에 다음 라인을 추가한다.


-Dfile.encoding=UTF8



파일 저장 후에 이클립스를 재시작하면 잘 나온다.


sbtplugin


/project 디렉토리에 assembly.sbt 넣기


$ more assembly.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1")


$ more build.sbt
name := "MySampleJar"

version := "1.0"

scalaVersion := "2.10.1"

libraryDependencies ++= Seq (
        "org.apache.spark" %% "spark-core" % "1.6.0" % "provided",
        "org.apache.spark" %% "spark-sql" % "1.6.0" % "provided"
)

jarName in assembly := "my-sample.jar"

// Scala 라이브러리를 제외하고 싶은 경우 추가
assemblyOption in assembly :=
        (assemblyOption in assembly).value.copy(includeScala = false)


다음 커맨드를 실행하면 모든 파일이 묶여서 하나의 jar로 만들어진다.
$ sbt assembly

참고: https://github.com/sbt/sbt-assembly



테스트를 건너띄고 합치고 싶은 경우는 다음과 같이 하면 된다.


sbt "set test in assembly := {}" clean assembly


참고: http://stackoverflow.com/questions/26499444/how-run-sbt-assembly-command-without-tests-from-command-line


.scala 파일 맨 앞줄에 shebang을 넣어준다.


  1 #!/usr/bin/env scala

  2

  3 println("hello")


파일 속성을 변경해준다.


$ chmod a+x test.scala


이제 바로 실행이 된다.


$ ./test.scala

hello



$ sbt 'inspect sbtVersion'

[info] Set current project to gilbird (in build file:/C:/cygwin64/home/gilbird/)

[info] Setting: java.lang.String = 0.13.9

[info] Description:

[info]  Provides the version of sbt.  This setting should be not be modified.

[info] Provided by:

[info]  */*:sbtVersion

[info] Defined at:

[info]  (sbt.Defaults) Defaults.scala:135

[info] Delegates:

[info]  *:sbtVersion

[info]  {.}/*:sbtVersion

[info]  */*:sbtVersion

[info] Related:

[info]  */*:sbtVersion




$ sbt sbt-version

[info] Set current project to root--sbt (in build file:/C:/cygwin64/home/gilbird/.sbt/)

[info] 0.13.9

sdk manager를 이용하여 scala를 간단하게 설치해보자.


먼저 sdkman을 설치한다.


$ curl -s get.sdkman.io | bash


그 다음 새창을 열거나 다음 커맨드로 sdkman을 실행가능하도록 한다.

$ source "$HOME/.sdkman/bin/sdkman-init.sh"
다음 커맨드로 scala를 설치하면 작업 완료이다.

[~]# sdk install scala

==== BROADCAST =================================================================

* 08/02/16: Gradle 2.11 released on SDKMAN! #gradle

* 06/02/16: Vertx 3.2.1 released on SDKMAN! #vertx

* 04/02/16: Kotlin 1.0.0-rc-1036 released on SDKMAN! #kotlin

================================================================================


Downloading: scala 2.11.7


  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0

100 27.1M  100 27.1M    0     0   360k      0  0:01:17  0:01:17 --:--:--  344k


Installing: scala 2.11.7

Done installing!


Do you want scala 2.11.7 to be set as default? (Y/n):  y


Setting scala 2.11.7 as default.

[~]# which scala

/Users/gilbird/.sdkman/candidates/scala/current/bin/scala




+ Recent posts