오미니언 마이닝
- 형태소 분석은 품사를 뽑아내어 형태소의 어근, 어미를 파악하는 것으로 한국 밖에 없습니다.
- 데이터 사전
# 다양한 단어들이 모인 집단입니다.
# 형태소 분석을 통해 분석된 형태소와 데이터 사전과 매칭하여 데이터를 분석합니다.(데이터 사전은 정보이고, 정보는 곧 돈이기 때문에 오픈이 잘 안 됩니다.
# 일반적으로 JSON 형태로 데이터를 처리합니다.
데이터 베이스에 사용할 데이터를 유형에 맞게 생성해 줬습니다.
- 데이터를 넣어주었습니다.
- 데이터가 얼마나 들어갔는지 확인해 보면 14811개가 들어가있는 것을 확인할 수 있습니다.
select count(*) from NLP_DICTIONARY;
- SQL count (카운트) 쿼리니다.
- 만약 제대로 데이터가 안 들어가면 테이블에 있는 데이터를 지우고 다시 시도해 봅니다.
delete from NLP_DICTIONARY
- 긍정이면 + , 부정이면 - 로 설정되어 있습니다.
- "폴킴의 최신 노래는 모든 날, 모든 순간은 감명적이다"라는 문장을 입력했을 때 분석을 하면 3어절씩 묶어서 분석을 합니다. "폴킴의 최신 노래는" + "최신 노래는 모든" + 노래는 모든 날," - - - -이렇게 쭉 계산되어 이 문장이 긍정적인 문장인지 부정적인 문장인지 구분할 수 있게 됩니다.
- 문장을 분석하기 위해 계속 DB를 조회하는 방법은 DB서버에 부하를 주어 부적합한 방법입니다. 때문에 데이터 사전을 메모리에 올려두고 메모리에서 가져다 사용하는 것이 바람직합니다.( 이렇게 사용할 수 있는 이유는 데이터 사전은 변경이 빈번하게 발생하지 않기때문에 가능합니다.)
- @PostConstruct 어노테이션을 사용합니다. 이 어노테이션은 스프링 켜질 때 최초로 딱 한 번 실행되고 그 뒤로는 실행되지 않습니다. ( 메모리에 딱 한 번 올립니다. 이때, 함수는 한 개만 사용할 수 있습니다.)
- 문장의 단어 모음 (3어절)마다 매번 데이터 사전의 데이터만큼 반복하는 것은 불필요합니다. 이런 불필요한 연산을 최소화 하기 위해 단어 모음의 첫 초성을 기반으로 데이터 사전을 객체화 해야 합니다.
- 데이터 사전 구성을 ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅅ --- 순으로 초성대로 데이터 사전을 구성합니다.
package poly.dto;
public class NlpDTO {
// DB 컬럼값
private String word; //단어
private String word_root; //단어 어근
private String polarity; //단어
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public String getWord_root() {
return word_root;
}
public void setWord_root(String word_root) {
this.word_root = word_root;
}
public String getPolarity() {
return polarity;
}
public void setPolarity(String polarity) {
this.polarity = polarity;
}
}
DTO
package poly.persistance.mapper;
import java.util.List;
import config.Mapper;
import poly.dto.NlpDTO;
@Mapper("NlpMapper")
public interface INlpMapper {
//단어 정보 가져오기
List<NlpDTO> getWord(NlpDTO pDTO) throws Exception;
}
IMapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- JAVA와 연결할 Mapper 파일 설정 -->
<mapper namespace="poly.persistance.mapper.INlpMapper">
<!-- 단어 정보 가져오기 -->
<select id="getWord" parameterType="NlpDTO" resultType="NlpDTO">
SELECT
WORD, WORD_ROOT, POLARITY
FROM
NLP_DICTIONARY
WHERE 1=1
<if test='word.equals("ㄱ")'>
<![CDATA[
AND WORD >= '가' AND WORD <= '깋'
]]>
</if>
<if test='word.equals("ㄲ")'>
<![CDATA[
AND WORD >= '까' AND WORD <= '낗'
]]>
</if>
<if test='word.equals("ㄴ")'>
<![CDATA[
AND WORD >= '나' AND WORD <= '닣'
]]>
</if>
<if test='word.equals("ㄷ")'>
<![CDATA[
AND WORD >= '다' AND WORD <= '딯'
]]>
</if>
<if test='word.equals("ㄸ")'>
<![CDATA[
AND WORD >= '따' AND WORD <= '띻'
]]>
</if>
<if test='word.equals("ㄹ")'>
<![CDATA[
AND WORD >= '라' AND WORD <= '맇'
]]>
</if>
<if test='word.equals("ㅁ")'>
<![CDATA[
AND WORD >= '마' AND WORD <= '밓'
]]>
</if>
<if test='word.equals("ㅂ")'>
<![CDATA[
AND WORD >= '바' AND WORD <= '빟'
]]>
</if>
<if test='word.equals("ㅃ")'>
<![CDATA[
AND WORD >= '빠' AND WORD <= '삫'
]]>
</if>
<if test='word.equals("ㅅ")'>
<![CDATA[
AND WORD >= '사' AND WORD <= '싷'
]]>
</if>
<if test='word.equals("ㅆ")'>
<![CDATA[
AND WORD >= '싸' AND WORD <= '앃'
]]>
</if>
<if test='word.equals("ㅇ")'>
<![CDATA[
AND WORD >= '아' AND WORD <= '잏'
]]>
</if>
<if test='word.equals("ㅈ")'>
<![CDATA[
AND WORD >= '자' AND WORD <= '칳'
]]>
</if>
<if test='word.equals("ㅉ")'>
<![CDATA[
AND WORD >= '짜' AND WORD <= '찧'
]]>
</if>
<if test='word.equals("ㅊ")'>
<![CDATA[
AND WORD >= '차' AND WORD <= '칳'
]]>
</if>
<if test='word.equals("ㅌ")'>
<![CDATA[
AND WORD >= '타' AND WORD <= '팋'
]]>
</if>
<if test='word.equals("ㅍ")'>
<![CDATA[
AND WORD >= '파' AND WORD <= '핗'
]]>
</if>
<if test='word.equals("ㅎ")'>
<![CDATA[
AND WORD >= '하' AND WORD <= '힣'
]]>
</if>
</select>
</mapper>
Mapper
package poly.util;
public class StringUtil {
// 데이터 사전에 정의된 키 값을 가져오기 위한 함
public static String getFirstWord(String word) {
String res = "";
if (word.matches("[가-깋]")) {
res = "ㄱ";
}else if (word.matches("[까-낗]")) {
res = "ㄲ";
}else if (word.matches("[나-닣]")) {
res = "ㄴ";
}else if (word.matches("[다-딯]")) {
res = "ㄷ";
}else if (word.matches("[따-띻]")) {
res = "ㄸ";
}else if (word.matches("[라-맇]")) {
res = "ㄹ";
}else if (word.matches("[마-밓]")) {
res = "ㅁ";
}else if (word.matches("[바-빟]")) {
res = "ㅂ";
}else if (word.matches("[빠-삫]")) {
res = "ㅃ";
}else if (word.matches("[사-싷]")) {
res = "ㅅ";
}else if (word.matches("[싸-앃]")) {
res = "ㅆ";
}else if (word.matches("[아-잏]")) {
res = "ㅇ";
}else if (word.matches("[자-짛]")) {
res = "ㅈ";
}else if (word.matches("[짜-찧]")) {
res = "ㅉ";
}else if (word.matches("[차-칳]")) {
res = "ㅊ";
}else if (word.matches("[카-킿]")) {
res = "ㅋ";
}else if (word.matches("[타-팋]")) {
res = "ㅌ";
}else if (word.matches("[파-핗]")) {
res = "ㅍ";
}else if (word.matches("[하-힣]")) {
res = "ㅎ";
}
return res;
}
}
StringUtil
package poly.service;
import poly.dto.NlpDTO;
public interface INlpService {
//단어 정보 가져오기
void getWord() throws Exception;
int preProcessWordAnalysisForMind(NlpDTO pDTO) throws Exception;
//감정 분석
int WordAnalysisForMind(String firstWord, String text) throws Exception;
}
IService
package poly.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import poly.dto.NlpDTO;
import poly.persistance.mapper.INlpMapper;
import poly.service.INlpService;
import poly.util.CmmUtil;
import poly.util.StringUtil;
@Service("NlpService")
public class NlpService implements INlpService {
private Logger log = Logger.getLogger(this.getClass());
@Resource(name = "NlpMapper")
private INlpMapper nlpMapper;
//NLP_DIC를 ㄱㄴㄷㄹ 순으로 나눠서 저장한 이유는 전체 약 몇 만건의 데이터를 조회하는 것 보다.
// 일정한 범위를 지정하여 데이터 조회 횟수를 감소하기 위해서 나눔
// 가나다 순으로 저장될 데이터 사전들(가~하까지)
private Map<String, List<NlpDTO>> NLP_DIC = new HashMap<String, List<NlpDTO>>();
@Override
@PostConstruct
public void getWord() throws Exception {
log.info(this.getClass().getName() + ".getWord start !!");
// 데이터 사전 조회하기 위한 변수를 저장할 DTO(ㄱ,ㄴ,ㄷ,ㄹ..저장함)
NlpDTO pDTO = new NlpDTO();
//가나다 별 데이터가 저장될 변수
List<NlpDTO> rList = null;
// ㄱ실행
pDTO.setWord("ㄱ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄱ", rList);
// ㄴ실행
pDTO.setWord("ㄴ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄴ", rList);
// ㄷ실행
pDTO.setWord("ㄷ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄷ", rList);
// ㄹ실행
pDTO.setWord("ㄹ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄹ", rList);
// ㅁ실행
pDTO.setWord("ㅁ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅁ", rList);
// ㅂ실행
pDTO.setWord("ㅂ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅂ", rList);
// ㅅ실행
pDTO.setWord("ㅅ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅅ", rList);
// ㅇ실행
pDTO.setWord("ㅇ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅇ", rList);
// ㅈ실행
pDTO.setWord("ㅈ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅈ", rList);
// ㅊ실행
pDTO.setWord("ㅊ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅊ", rList);
// ㅋ실행
pDTO.setWord("ㅋ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅋ", rList);
// ㅌ실행
pDTO.setWord("ㅌ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅌ", rList);
// ㅍ실행
pDTO.setWord("ㅍ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅍ", rList);
// ㅎ실행
pDTO.setWord("ㅎ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅎ", rList);
// ㄲ실행
pDTO.setWord("ㄲ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄲ", rList);
// ㄸ실행
pDTO.setWord("ㄸ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㄸ", rList);
// ㅃ실행
pDTO.setWord("ㅃ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅃ", rList);
// ㅆ실행
pDTO.setWord("ㅆ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅆ", rList);
// ㅉ실행
pDTO.setWord("ㅉ");
rList = nlpMapper.getWord(pDTO); //DB 조회
if(rList ==null) {
rList = new ArrayList<NlpDTO>();
}
NLP_DIC.put("ㅉ", rList);
log.info(this.getClass().getName() + ".geWord End ! !");
}
// 감정 분석을 위한 문장 나누는 전처리 단계
@Override
public int preProcessWordAnalysisForMind(NlpDTO pDTO) throws Exception {
log.info(this.getClass().getName() + ".wordAnalysisForMind start ! !");
int res = 0;
//분석할 문장 ( 특수 문자 모두 제거 )
String text = CmmUtil.nvl(pDTO.getWord()).replaceAll("[^\\uAC00-\\uD7A3xfe0-9a-zA-Z\\\\s]", " ");
// 연속된 스페이스(공백) 제거
text = text.replaceAll("\\s{2,}", " ");
log.info("text : " + text);
//공백으로 단어를 나누기
String[] textArr = text.split(" ");
log.info("textArr.length : " + textArr.length);
//데이터 사전의 단어의 수가 최대 3로 정의
if(textArr.length < 4) {
//문장의 첫 글자
String firstWord = textArr[0].substring(0, 1);
//데이터 분석 진행
res = WordAnalysisForMind(firstWord, text);
}else {
//최대 반복 횟수
int maxCnt = textArr.length - 2;
log.info("###textArr.length : " + textArr.length);
log.info("###maxCnt : " + maxCnt);
for(int i = 0; i < maxCnt; i++) {
// 문장의 첫 글자
String firstWord2 = textArr[i].substring(0, 1);
log.info("###반복 횟수 : " + i);
String text2 = textArr[i] + " " + textArr[i + 1] + " " + textArr[i +2];
res += WordAnalysisForMind(firstWord2, text2);
}
}
log.info("Res : " + res);
//로그찍기
log.info(this.getClass().getName() + ".WordAnalysisForMind End !");
return res;
}
//감정 분석 처리 함수
@Override
public int WordAnalysisForMind(String firstWord, String text) throws Exception {
int res = 0;
log.info("firstWord : " + firstWord);
log.info("text : " + text);
//데이터 사전 종류
String dicType = StringUtil.getFirstWord(firstWord);
log.info("DIC type : " + dicType);
//데이터 사전에 존재하는 것만 분석 수행
if (dicType.length() > 0) {
//문장의 첫글자를 통해 해당되는 데이터 사전가져오기
List<NlpDTO> rList = NLP_DIC.get(StringUtil.getFirstWord(firstWord));
if (rList ==null) {
rList = new ArrayList<NlpDTO>();
}
Iterator<NlpDTO> it = rList.iterator();
while(it.hasNext()) {
NlpDTO rDTO = it.next();
if(rDTO == null) {
rDTO = new NlpDTO();
}
//일치하는 값이 존재한다면,
if(text.indexOf(CmmUtil.nvl(rDTO.getWord())) > -1) {
log.info("DIC-word : " + CmmUtil.nvl(rDTO.getWord()));
log.info("DIC-word getPolarity : " + CmmUtil.nvl(rDTO.getPolarity()));
log.info("text : " + text);
res += Integer.parseInt(CmmUtil.nvl(rDTO.getPolarity(), "0"));
//데이터 사전에 검색이 되어있기 때문에 더 이상 while문을 실행하지 않는다.
break;
}
}
}
return res;
}
}
Service
package poly.controller;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import poly.dto.NlpDTO;
import poly.service.INlpService;
import poly.util.CmmUtil;
@Controller("NlpController")
public class NlpController {
private Logger log = Logger.getLogger(this.getClass());
@Resource(name = "NlpService")
private INlpService nlpService;
//문자 입력창
@RequestMapping(value = "nlp/inputForm")
public String inputForm() {
log.info(this.getClass().getName() + ".inputForm !");
return "/nlp/inputForm";
}
//긍정, 부정 분석하기
@RequestMapping(value = "nlp/wordAnalysis")
public String wordAnalysis(HttpServletRequest request, HttpServletResponse response,
ModelMap model) throws Exception {
log.info(this.getClass().getName() + ".wordAnalysis start !");
String res = "";
//분석할 문장
String text_message = CmmUtil.nvl(request.getParameter("text_message"));
log.info("######################"+text_message);
NlpDTO pDTO = new NlpDTO();
pDTO.setWord(text_message);
int point = nlpService.preProcessWordAnalysisForMind(pDTO);
if(point < 0) {
res = "\"" + text_message + "\" 문장의 분석결과는 " + point + "로 부정적인 결과가 나왔습니다.";
}else if (point == 0) {
res = "\"" + text_message + "\" 문장의 분석결과는 데이터 사전에 존재하지 않아 분석이 불가능합니다.";
}else {
res = "\"" + text_message + "\" 문장의 분석결과는" + point + "로 긍정적인 결과가 나왔습니다.";
}
model.addAttribute("res", res);
log.info(this.getClass().getName() + ".wordAnalysis end !");
return "/nlp/wordAnalysis";
}
}
Controller
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>감정 분석을 위한 문장 입력폼</title>
</head>
<body>
<h2>오피니언 마이닝 - 감정 분석</h2>
<hr/>
<form name = "form1" method = "post" action = "/nlp/wordAnalysis.do">
<br/>
분석 메시지<br/>
<input type = "text" name = "text_message" style = "width:400px"/>
<br/>
<br/>
<input type = "submit" value = "전송"/>
</form>
</body>
</html>
inputForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import = "poly.util.CmmUtil" %>
<%
//Controller로부터 전달 받은 데이터
String res = CmmUtil.nvl((String) request.getAttribute("res"));
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>감정 분석 결과</title>
</head>
<body>
<h2>감정 분석 결과</h2>
<hr/>
<br/>
<%=res %>
</body>
</html>
wordAnalysis.jsp
- 주의점 : 코드는 완성 했는데 값을 계속 ???로 받아오는 오류가 있었습니다.
인코딩을 UTF-8로 전부 다 바꿔주니 해결되었습니다.
'Framework & Library > Spring Framework' 카테고리의 다른 글
[Spring Framework] : Rest 기반 Open API Server 구현 (3부) (0) | 2021.11.03 |
---|---|
[Spring Framework] : Rest 기반 Open API Server 구현 (2부) (0) | 2021.11.03 |
[Spring Framework] : Rest 기반 Open API Server 구현 (1부) (0) | 2021.11.03 |
[Spring Framework] : 딥러닝 (이미지 인식) (0) | 2021.10.27 |
[Spring Framework] : 웹 크롤링(수집)하기 (0) | 2021.10.26 |
댓글