<< Bayes' Theorem과 Mahout를 활용한 스팸 필터링 | Home | K-Means Clustering >>

Sentiment Analysis

보통 Sentiment Analysis은 텍스트에 포함된 내용이 주관적(Subjective)인지 객관적(Objective)인지를 먼저 판별하고, 주관적이면 극성(Polarity)을 분석하여 내용이 긍정적(Positive)인지 부정적(Negative)인지 판별하고 나서 상품이나 브랜드 등의 여론이 긍정인지, 부정인지를 찾아내는데 주로 활용된다.
특히 Brand Reputation, 정치 등 다양한 여론을 수렴할 때 많이 사용된다.

1. 긍정/부정을 판별하는 방법들
- SVMs(Support Vector Machines): 미리 사전에 긍정/부정으로 분류된 학습 데이터(Training Sets)로 텍스트의 긍정/부정 의견을 분류하는 방식.
- N-grams or Part Of Speech: N-grams 단어 구조로 긍정/부정을 찾는 방식. "I do not like to drink tea"라는 문장이 있고 이 문장을 Bi-gram Decomposition으로 분리하면, "I-do", "do-not", "not-like"와 같은 식으로 분리하고 N은 주로 1, 2, 3까지 사용한다.
- Lexicon-based Approach: 사전에 미리 정의된 긍정/부정 Bag of Words(1-grams or Uni-grams)를 이용하여 텍스트에 포함된 긍정/부정 단어의 출현 빈도로 긍정과 부정을 판별하는 기법. LIWC(Linguistic Inquiry andWord Count)나 POMS(Profile of Mood States) 같은 사전을 이용할 수 있는데 한국어에서는 사용할 수 없다. 긍정/부정 코퍼스(Corpus)를 만드는 것이 관건이라고 할 수 있다.
- Linguistic Approach: 텍스트의 문법적인 구조를 파악하여 극성을 판별하는 기법이다. 주로 Lexicon-based Approach 방식과 함께 사용한다. 문맥(context) 등을 파악하여 극성을 판별한다.

2. Logistic Regression
Event Data를 모델링할때 Supervised learning과 Unsupervised learning으로 나눈다. Supervised learning은 분류되어야 할 대상을 미리 알고 Train이란 학습 과정을 거쳐 어떤 대상에 속하는지 분류하는 것이고, 즉 입출력(input-output)의 쌍으로 구성된 training set으로부터 입력을 출력을 사상하는 함수를 학습하는 과정이고 Unsupervised learning은 분류하려는 대상의 정보를 모르는 상태에서 다양한 부류로 분류하는 것, 즉 출력값 없이 오직 입력값만 주어지며, 이러한 입력값들의 공통적인 특성을 파악하여 학습하는 과정을 말한다.
Supervised learning의 예는 Decision trees, Regression analysis, Neural networks, Naive Bayesian Classification, Support Vector Machine, Case Based Reasoning 등이 해당되고 Unsupervised learning의 예는 Clustering(K-Means) 이 해당된다.
Logistic Regression은 단지 두개의 값만을 가지는 종속변수와 독립변수들 간의 인과 관계를 로지스틱 함수를 이용하여 추정하는 통계 기법. 즉, 자료가 두그룹으로 나누어진 상황에서 두 그룹이 구분되는 특성을 파악하여 다른 어떤 데이터가 어느 그룹에 속하는지 예측하는 방식이다.
예측은 Maximum Likelihood Method 방식으로 추정한다. 아래는 샘플 예제는 예측은 Logistic Regression 방식을, Train model을 위한 classification 알고리즘인 stochastic gradient descent(SGD)를 사용 했다.

3. 샘플 소스 컴파일 및 데이터 준비
- 샘플 데이터는 http://www.csie.ntu.edu.tw/~cjlin/libsvm/에서 다운 로드.
> tar -xjf review-imdb.tbz2 
> tar -xjf review-polarity.tbz2
# 데이터는 review-imdb에는 50,000 리뷰(평판)가 tran과 test 25k씩 나눠져 있음, 
 부정과 긍정도 마찬가지임.

- SentimentModelTrainer.java
public static void main(final String[] args) throws IOException {
	final File base = new File(args[0]);
	final String modelPath = args.length > 1 ? args[1] : "target/model";
	final Multiset overallCounts = HashMultiset.create();
	final Dictionary newsGroups = new Dictionary();

	final SentimentModelHelper helper = new SentimentModelHelper();
	helper.getEncoder().setProbes(2);
	final AdaptiveLogisticRegression learningAlgorithm = 
	 new AdaptiveLogisticRegression(
			2, SentimentModelHelper.FEATURES, new L1());
	learningAlgorithm.setInterval(800);
	learningAlgorithm.setAveragingWindow(500);

	final List files = Lists.newArrayList();
	for (final File newsgroup : base.listFiles()) {
		if (newsgroup.isDirectory()) {
			newsGroups.intern(newsgroup.getName());
			files.addAll(Arrays.asList(newsgroup.listFiles()));
		}
	}
	Collections.shuffle(files);
	System.out.printf("%d training files\n", files.size());
	final SGDInfo info = new SGDInfo();

	int k = 0;

	for (final File file : files) {
		final String ng = file.getParentFile().getName();
		final int actual = newsGroups.intern(ng);

		final Vector v = helper.encodeFeatureVector(file, overallCounts);
		learningAlgorithm.train(actual, v);

		k++;
		final State best = learningAlgorithm.getBest();
		SGDHelper.analyzeState(info, 0, k, best);
	}
	learningAlgorithm.close();
	SGDHelper.dissect(0, newsGroups, learningAlgorithm, files,
			overallCounts);
	System.out.println("exiting main");

	ModelSerializer.writeBinary(modelPath, learningAlgorithm.getBest()
			.getPayload().getLearner().getModels().get(0));

	final List counts = Lists.newArrayList();
	System.out.printf("Word counts\n");
	for (final String count : overallCounts.elementSet()) {
		counts.add(overallCounts.count(count));
	}
	Collections.sort(counts, Ordering.natural().reverse());
	k = 0;
	for (final Integer count : counts) {
		System.out.printf("%d\t%d\n", k, count);
		k++;
		if (k > 1000) {
			break;
		}
	}
}

- SentimentModelTester.java
public void run(final PrintWriter output) throws IOException {
	final File base = new File(inputFile);
	// contains the best model
	final OnlineLogisticRegression classifier = ModelSerializer.readBinary(
	new FileInputStream(modelFile), OnlineLogisticRegression.class);

	final Dictionary newsGroups = new Dictionary();
	final Multiset overallCounts = HashMultiset.create();

	final List files = Lists.newArrayList();
	for (final File newsgroup : base.listFiles()) {
		if (newsgroup.isDirectory()) {
			newsGroups.intern(newsgroup.getName());
			files.addAll(Arrays.asList(newsgroup.listFiles()));
		}
	}
	System.out.printf("%d test files\n", files.size());
	final ResultAnalyzer ra = new ResultAnalyzer(newsGroups.values(),
			"DEFAULT");
	for (final File file : files) {
		final String ng = file.getParentFile().getName();

		final int actual = newsGroups.intern(ng);
		final SentimentModelHelper helper = new SentimentModelHelper();
		final Vector input = helper
				.encodeFeatureVector(file, overallCounts);

		final Vector result = classifier.classifyFull(input);
		final int cat = result.maxValueIndex();
		final double score = result.maxValue();
		final double ll = classifier.logLikelihood(actual, input);
		final ClassifierResult cr = new ClassifierResult(newsGroups
				.values().get(cat), score, ll);
		ra.addInstance(newsGroups.values().get(actual), cr);

	}
	output.printf("%s\n\n", ra.toString());
}

4. 실행 및 결과
- Train Model 생성
mvn -e exec:java -Dexec.mainClass="com.mimul.sentiment.SentimentModelTrainer"
  -Dexec.args="/mahout-work-k2/sentiment/review-imdb/train 
  /mahout-work-k2/sentiment/model/review-imdb.txt"
mvn -e exec:java -Dexec.mainClass="com.mimul.sentiment.SentimentModelTrainer"
  -Dexec.args="/mahout-work-k2/sentiment/review-polarity/train 
  /mahout-work-k2/sentiment/model/review-polarity.txt"

- 긍정/부정 확인
mvn -e exec:java -Dexec.mainClass="com.mimul.sentiment.SentimentModelTester"  
 -Dexec.args="--input /mahout-work-k2/sentiment/review-imdb/test/ 
 --model /mahout-work-k2/sentiment/model/review-imdb.txt"
25001 test files
=======================================================
Summary
-------------------------------------------------------
Correctly Classified Instances     :      20009       80.0328%
Incorrectly Classified Instances   :       4992       19.9672%
Total Classified Instances         :      25001

=======================================================
Confusion Matrix
-------------------------------------------------------
a       b       <--Classified as
9403    3098     |  12501       a     = neg
1894    10606    |  12500       b     = pos

mvn -e exec:java -Dexec.mainClass="com.mimul.sentiment.SentimentModelTester"
  -Dexec.args="--input /mahout-work-k2/sentiment/review-polarity/test/
   --model /mahout-work-k2/sentiment/model/review-polarity.txt"
2000 test files
=======================================================
Summary
-------------------------------------------------------
Correctly Classified Instances      :       1856          92.8%
Incorrectly Classified Instances    :        144           7.2%
Total Classified Instances          :       2000

=======================================================
Confusion Matrix
-------------------------------------------------------
a       b       <--Classified as
940     60       |  1000        a     = neg
84      916      |  1000        b     = pos

Avg. Log-likelihood: -0.3173586393308741 25%-ile: -0.4319689425433808
 75%-ile: -0.2660177819268935
거의 정확하게 반반 긍정/부정 결과가 나왔다. 이걸 응용해서 트트터 데이터를 분석해보면 좋은 의미가 될 거 같다.

[참조 사이트]


Re: Sentiment Analysis

 블로그 항상 잘보고 있습니다. 공유정신에 존경을.. 눈팅만 하다가 댓글을 남겨봅니다. 더 많은 포스팅의 에너지를 받으세요. ㅋㅋ

Avatar: redhat6

Re: Sentiment Analysis

 좋은 정보 공유하여 감사합니다.

Train model을 SGD를 사용하여 할때마다 결과가 바뀌는 군요

Train, Test 모두 SVM으로 해보고 싶은데 아직 제 실력이 부족하네요.

아무튼 잘 배웠습니다.


Add a comment Send a TrackBack