본문 바로가기
Python/Python 실전

[Python 감정분석] 영어 데이터 전처리 :: 마이자몽

by 마이자몽 🌻♚ 2019. 2. 9.

Python 감정 분석

텍스트로 부터 어떠한 주관적인 의견을 뽑아내는 것이 감정분석이다.
기본적으로 train data로 부터 데이터 분석을 하고  test data에 반영을 한다.
주로 긍정/부정 형식의 2진 답변을 반환하여  1/0 또는 1/-1의 값으로 긍정 부정을 판단한다.
각 언어별로 데이터 전처리하는 방법이 조금씩 다르다.


영어 데이터 셋 구하기

데이터 셋을 구하기 위해 참조하기 좋은 사이트에 대한 글이다.

이번 글에서는 캐글에서 트위터 감정분석을 위한 데이터 셋을 다운받아 전처리를 해볼거다.


해당 사이트에서 sentiment를 검색하면 각종 감정분석을 위한 데이터셋을 제공해준다.




트위터 글에 대한 test와 train 데이터를 받아준다.


영어 데이터 전처리

데이터 불러오기

header=0 :  파일 첫번째 행에 열 이름이 있다는 것을 인지
delimiter : 구분자
quoting=3 : 쌍따옴표를 무시하도록 한다.
1
2
3
4
5
6
7
import pandas as pd
 
train = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/train.tsv',
                        header=0, delimiter='\t', quoting=3)
 
test = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/test.tsv',
                       header=0, delimiter='\t', quoting=3)
cs

의미있는 영어 데이터를 받아오기 위해서 4~5단계의 데이터 가공처리가 필요하다.
아래는 train data 에대한 raw data의 일부이다.

1. HTML 제거(있는 경우에만)

해당 데이터에서는 html태그가 없다.
HTML제거 작업이 필요 없지만, 필요한 데이터의 경우  BeautifulSoup를 이용해서 제거작업을 해주면 된다.
1
2
3
from bs4 import BeautifulSoup
 
raw_tweet = BeautifulSoup(raw_tweet, 'html.parser').get_text()
cs

2. 영어가 아닌 문자를 공백으로 대체

영어가 아닌 문자는 제거하여 단어판 남기도록 한다.

이때 정규표현식을 사용하여 제거해준다.

1
2
3
4
import re
 
# 영문자 이외 문자는 공백으로 변환
only_english = re.sub('[^a-zA-Z]'' ', data)
cs

3. 대문자는 소문자로 변환

영어의 경우 문장의 시작이나 고유명사는 대문자로 시작하여 분석할때 "Apple"과 "apple"을 다른 단어 취급하게된다.

모든 단어를 소문자로 변환한다.

1
2
# 소문자 변환
no_capitals = only_english.lower().split()
cs

4. 불용어 제거

학습 모델에서 예측이나 학습에 실제로 기여하지 않는 텍스트를 불용어라고한다.
I, that, is, the, a  등과 같이 자주 등장하는 단어이지만 실제로 의미를 찾는데 기여하지 않는 단어들을 제거하는 작업이 필요하다.
1
2
3
4
5
from nltk.corpus import stopwords
 
# 불용어 제거
stops = set(stopwords.words('english'))
no_stops = [word for word in no_capitals if not word in stops]
cs

nltk stopwords관련 에러가 발생한다면 nltk download 작업을 코드상으로 해준다.
1
nltk.download('stopwords')
cs

5. 어간 추출

see, saw, seen

run, running, ran

위 예시처럼 어형이 과거형이든 미래형이든 하나의 단어로 취급하기 위한 처리작업이다.

nltk에서 제공하는 형태소 분석기를 사용하는데 여러가지 어간추출 알고리즘(Porter, Lancaster, Snowball 등등)이 존재한다.

1
2
3
4
5
import nltk
 
# 어간 추출
stemmer = nltk.stem.SnowballStemmer('english')
stemmer_words = [stemmer.stem(word) for word in no_stops]
cs

전처리 전체 작업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def data_text_cleaning(data):
 
    # 영문자 이외 문자는 공백으로 변환
    only_english = re.sub('[^a-zA-Z]'' ', data)
 
    # 소문자 변환
    no_capitals = only_english.lower().split()
 
    # 불용어 제거
    stops = set(stopwords.words('english'))
    no_stops = [word for word in no_capitals if not word in stops]
 
    # 어간 추출
    stemmer = nltk.stem.SnowballStemmer('english')
    stemmer_words = [stemmer.stem(word) for word in no_stops]
 
    # 공백으로 구분된 문자열로 결합하여 결과 반환
    return ' '.join(stemmer_words)

cs


멀티프로세싱 작업

해당 데이터는 전처리 작업하는데 오래 걸리지 않는다.
데이터 수, 데이터 길이가 늘어날수록 PC사양에 따라 전처리 작업 시간 차이가 많아난다.
전처리 작업만 진행하는데 몇분씩 걸릴 수 있다.
이러한 작업을 빠르게 진행해주기 위해서 멀티프로세싱으로 작업을 진행한다.
1
2
3
4
5
6
7
from multiprocessing import Pool
 
def use_multiprocess(func, iter, workers):
    pool = Pool(processes=workers)
    result = pool.map(func, iter)
    pool.close()
    return result
cs


1
2
3
4
5
6
7
8
9
10
11
12
import time
 
if __name__ == '__main__':
    start_time = time.time()
    train = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/train.tsv',
                        header=0, delimiter='\t', quoting=3)
 
    test = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/test.tsv',
                       header=0, delimiter='\t', quoting=3)
 
    clean_processed_tweet = use_multiprocess(data_text_cleaning, train['tweet'], 3)
    print('실행 시간 :', (time.time() - start_time))

cs



Tweet 단어 수 확인

seaborn을 사용해서 tweet별 단어수 와 고유단어 수를 시각화해서 확인해볼거다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import matplotlib.pyplot as plt
from matplotlib import rc
import seaborn as sns
 
def show_tweet_word_count_stat(data):
    num_word = []
    num_unique_words = []
    for item in data:
        num_word.append(len(str(item).split()))
        num_unique_words.append(len(set(str(item).split())))
 
    # 일반
    train['num_words'= pd.Series(num_word)
    # 중복 제거
    train['num_unique_words'= pd.Series(num_unique_words)
 
    x = data[0]
    x = str(x).split()
    print(len(x))
 
    rc('font', family='AppleGothic')
 
    fig, axes = plt.subplots(ncols=2)
    fig.set_size_inches(186)
    print('Tweet 단어 평균 값 : ', train['num_words'].mean())
    print('Tweet 단어 중간 값', train['num_words'].median())
    sns.distplot(train['num_words'], bins=100, ax=axes[0])
    axes[0].axvline(train['num_words'].median(), linestyle='dashed')
    axes[0].set_title('Tweet 단어 수 분포')
 
    print('Tweet 고유 단어 평균 값 : ', train['num_unique_words'].mean())
    print('Tweet 고유 단어 중간 값', train['num_unique_words'].median())
    sns.distplot(train['num_unique_words'], bins=100, color='g', ax=axes[1])
    axes[1].axvline(train['num_unique_words'].median(), linestyle='dashed')
    axes[1].set_title('Tweet 고유한 단어 수 분포')
 
    plt.show()
 
show_tweet_word_count_stat(clean_processed_tweet)
cs



결과

각 Tweet별 단어 최대 최소 수와 중간, 평균 값을 시각화하여 확인





전체 실행 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from multiprocessing import Pool
import pandas as pd
import re
import time
import nltk
from nltk.corpus import stopwords
import matplotlib.pyplot as plt
from matplotlib import rc
import seaborn as sns
 
 
def use_multiprocess(func, iter, workers):
    pool = Pool(processes=workers)
    result = pool.map(func, iter)
    pool.close()
    return result
 
 
def check_basic_info():
    print("-----train-----")
    print(train.head())
    print(train.info())
    print(train['tweet'][0:10])
 
    print("\n\n-----test-----")
    print(test.head())
    print(test.info())
 
 
def data_text_cleaning(data):
 
    # 영문자 이외 문자는 공백으로 변환
    only_english = re.sub('[^a-zA-Z]'' ', data)
 
    # 소문자 변환
    no_capitals = only_english.lower().split()
 
    # 불용어 제거
    stops = set(stopwords.words('english'))
    no_stops = [word for word in no_capitals if not word in stops]
 
    # 어간 추출
    stemmer = nltk.stem.SnowballStemmer('english')
    stemmer_words = [stemmer.stem(word) for word in no_stops]
 
    # 공백으로 구분된 문자열로 결합하여 결과 반환
    return ' '.join(stemmer_words)
 
 
def show_tweet_word_count_stat(data):
    num_word = []
    num_unique_words = []
    for item in data:
        num_word.append(len(str(item).split()))
        num_unique_words.append(len(set(str(item).split())))
 
    # 일반
    train['num_words'= pd.Series(num_word)
    # 중복 제거
    train['num_unique_words'= pd.Series(num_unique_words)
 
    x = data[0]
    x = str(x).split()
    print(len(x))
 
    rc('font', family='AppleGothic')
 
    fig, axes = plt.subplots(ncols=2)
    fig.set_size_inches(186)
    print('Tweet 단어 평균 값 : ', train['num_words'].mean())
    print('Tweet 단어 중간 값', train['num_words'].median())
    sns.distplot(train['num_words'], bins=100, ax=axes[0])
    axes[0].axvline(train['num_words'].median(), linestyle='dashed')
    axes[0].set_title('Tweet 단어 수 분포')
 
    print('Tweet 고유 단어 평균 값 : ', train['num_unique_words'].mean())
    print('Tweet 고유 단어 중간 값', train['num_unique_words'].median())
    sns.distplot(train['num_unique_words'], bins=100, color='g', ax=axes[1])
    axes[1].axvline(train['num_unique_words'].median(), linestyle='dashed')
    axes[1].set_title('Tweet 고유한 단어 수 분포')
 
    plt.show()
 
 
if __name__ == '__main__':
    start_time = time.time()
    train = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/train.tsv',
                        header=0, delimiter='\t', quoting=3)
 
    test = pd.read_csv('/Users/Jamong/Desktop/data/twitter_sentiment/test.tsv',
                       header=0, delimiter='\t', quoting=3)
 
    check_basic_info()
    clean_processed_tweet = use_multiprocess(data_text_cleaning, train['tweet'], 3)
    print('실행 시간 :', (time.time() - start_time))
 
    show_tweet_word_count_stat(clean_processed_tweet)
cs


댓글0