1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

自作自演。Dockerで簡単にセットアップして、スクレイピングし、多クラス分類をしてみる。

Posted at

#はじめに
Qiitaの投稿に慣れていきます。。

Dockerによる環境構築やAWSによる、「PCローカル環境依存」への脱却をしようとしているこの頃。
いろんな学習をしているうちに、ローカルにタコ足配線みたく崇高な設定だらけになって、
もっとシンプルにそして、PCの買い替えの時に困らないようにしたいよね〜という人も少なくないはず

Docker構築

コンテナ上で作業

スクレイピングとかでデータ収集

せっかくなので、多クラス分類

をしてみました。

全てgit上に上げているのと、今後gensimとか使って各評価の文章要約でもしようかと思っています。
また、その時に書きます。

https://github.com/Yu0130fri/Scraping-NLP

この投稿だけでDocker完璧!とか、多クラス分類できるようになる!
みたいな崇高な記事ではないことも先に書いておきます(^^;)

#Dockerfileによる環境構築
自分の勝手な主題としてdockerfileやdockerhubからのpullで(簡単に誰でも)環境を構築できるように練習したいのが主題。
今回スクレイピングで楽天トラベルのサイトからレビューを持ってくるため、形態素解析にMeCabやneologdなどを個々人でセットアップするのだるいよね〜〜
という背景があり、Dockerなら共通でいけるやん〜みたいな感じでdockerの勉強がてら作りました。

gitコード上のDockerフォルダにDockerfileは作っていますが、普通に

latest

でいけます。
dockerのimageは重くなりがち(たしか今回使うイメージも7GB。。。)なので、
ローカルのストレージが足りないときは AWS上で実行するのがおすすめなんですが、
AWS上でdockerを使えるようにしないといけなかったりもあるので、任せますw

とはいえ、常にimageを残す必要もないので、実行は

8888 -v (作業ファイルのある場所):/(お好きな作業環境名) --rm dockyupy/nlp-mecab-python:latest

みたいに--rmコマンドで終了したら削除するようにして、その後rmiコマンドですぐに消すとかが個人的推奨。

一応、Dockerfileの中身を入れときます。

FROM ubuntu:latest
RUN apt update && apt install -y \
	curl \
	file \
	git \
	libmecab-dev \
	make \
	mecab \
	mecab-ipadic-utf8 \
	sudo \
	wget \
	vim \
	xz-utils

# install anaconda3 you can change the version if you want to 
WORKDIR /opt
RUN wget https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh && \
	sh Anaconda3-2021.05-Linux-x86_64.sh -b -p /opt/anaconda3 && \
	rm -f Anaconda3-2021.05-Linux-x86_64.sh

ENV PATH=/opt/anaconda3/bin:$PATH
RUN pip install --upgrade pip && \
	pip install mecab-python3 && \
	pip install mojimoji && \
	pip install unidic-lite
WORKDIR /
# get the ipadic-neologd
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git && \
	echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a
CMD ["jupyter", "lab", "--ip=0.0.0.0", "--allow-root", "--LabApp.token=''"]

anaconda のインストールから、MeCab・neologdのセットアップまでしていて、すぐに
import MeCab
を使えるようにしました。

#スクレイピング
特に複雑なスクレイピングコードでもなく、オーソドックスにrequests, bs4で取得したい項目を拾ってきています。
スクレイピングについてガツガツ解説するのは、のちのち。

def make_reputation_csv(AREA_URL='https://travel.rakuten.co.jp/yado/aichi/A.html'):
    try:
        soup_area = modules.get_soup(AREA_URL)
        hotels = soup_area.select('section > div.htlHead> div.info > span')


        for hotel in hotels:

            hotel_ID = hotel['id'][-6:]
            hotel_review_url = f'https://travel.rakuten.co.jp/HOTEL/{hotel_ID}/review.html'

            # get data souce
            soup_hotel = modules.get_soup(hotel_review_url)

            # hotel name
            hotel_name = soup_hotel.select("#RthNameArea > h2")[0].text

            _reputations, _comments, _purposes, _companions, _dates = modules.make_object_lists(soup_hotel)

            # make df and to csv
            data = pd.DataFrame(
                        {'HotelName': hotel_name,
                         'Date': _dates, 
                         'Reputations': _reputations, 
                         'Comments': _comments, 
                         'Purposes': _purposes, 
                         'companions': _companions}
                    ).to_csv(f'./sample_csv/{hotel_ID}_reputation.csv', encoding='utf-8', index=False)

            time.sleep(2)
    except:
        print('実行は終了されました')

このコードのsleep時間が短いかもなので、多用したらすぐにサイトから弾き出されるので、一回だけって感じです。

#多クラス分類
今回は精度向上の目標が本題ではないので、SVM(一応RandomizeSearchCVは使った), MultinomialNBでどのくらいの精度が出たのかくらいをみました。
全て RakutenTravel_sc_predict.ipynbで作業しているので、いろいろ追いやすいかなと思いますが、一応ここでも少し書いときます。

データ読み込み〜前処理

一応今回欠損値はcsvにはないので、補完作業はいらないです。
また、今回は分析に使わなかったですが、object-> datetimeに変換と
本題の形態素への変換だけ行いました。

# データ読み込みを一括で
csv_lists = glob('./sample_csv/*')
data_dict = {}
preffixes = 'data'
for i, csv in enumerate(csv_lists):
    data_dict[preffixes+str(i+1)] = pd.read_csv(csv)


data = pd.concat([data for data in data_dict.values()]).reset_index(drop=True)

多少癖のある読み込み方かもですが、いちいちread_csvするのも今回はめんどくさいので、
dictに格納し、一気に読み込ませました。
これによりいちいちcsvの名前を書き込む手間も、命名も簡単に行えるので、最近勉強したなかでかなり有意義なコードでしたので、ここでも共有しておきます

preffixを先に定義することで{data1: (Dataframe), data2: (DataFrame), ...}のように簡単に連番を振ることができます。

データ読み込めば、次は日付変換・形態素解析をしておきます。

data['Date'] = pd.to_datetime(data['Date'], format='%Y年%m月')
data['Comments'] = [word.strip() for word in data['Comments']] # 不要な\n, \tを削除
data['Comment_words'] = data['Comments'].apply(lambda x: text_to_word(x))

このtext_to_wordは文書を形態素解析して、stopwordの除去、全ての形態素を原型に置換する関数です。
割と個人的に便利と思っている関数

nlp_modules.py
tagger = MeCab.Tagger("-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd")
tagger.parse("")

def text_to_word(text, stopword_pass="./stopwords/Japanese.txt"):
    """
    Tokenize texts with MeCab neologd
    
    Parameters
    ----------
    text: str
        text to tokenize
    stopword_pass: str
        path of stopwords
        
    Returns
    -------
    basic_words_list: str
        tokenized and basic text
    
    """
    
    #stopwordのリスト作成
    stopword_list =[]
    with open(stopword_pass, "r") as file:
        lines = file.readlines()
            
        stopword_list = [stopword for stopword in lines if stopword.strip()]
    
    #mojimojiで全角数字、英字を半角に統一。大文字もすべて小文字に統一する
    text = mojimoji.zen_to_han(text, kana=False).lower()
    #解析
    parse_text = tagger.parse(text)
    
    #ここから解析したものの原型などを取り出して抽出していく
    basic_words_list = []
    
    #各単語の解析結果ごとにsplitしていく
    split_parse_text = parse_text.split("\n")
    
    for parse_word in split_parse_text:
        #\tで区切り、表層と品詞の情報に分ける
        split_parse_word = parse_word.split("\t")
        surface_word = split_parse_word[0]
        
        #最終行はEOSのため、終了させる
        if surface_word =="EOS":
            break
        else:
            #品詞が動詞、形容詞の場合、原形を格納
            morph_info = split_parse_word[1]
            morphs = morph_info.split(",")
            #品詞情報
            morph = morphs[0]
            #原型
            #原型は品詞情報の後ろから3番目
            basic = morphs[-3]
            
            if morph == "記号":
                continue
            elif morph in("動詞", "形容詞") and basic not in stopword_list:
                basic_words_list.append(basic)
                    
            elif morph =='名詞' and basic not in stopword_list:
                basic_words_list.append(basic)
        
    #最終的にまとめたものを半角スペースでjoinし、リストで返す
    basic_words_list = " ".join(basic_words_list)
    return basic_words_list

なっっがいですが、割とシンプルなコードかなと思います。
逐次的な説明は端折りますが、ここでMeCabとか使えるのはDockerのおかげw

#多クラス分類
前処理があらかた終わったので、分析へ
今回、Tfidfを使うので、sprase marixが出力として出てくるので、実際扱い方の練習にもなりました。

data_copy = data.copy()
X = data_copy[['Comment_words', 'Purposes', 'companions']]
X = pd.get_dummies(X, columns=['Purposes', 'companions'], drop_first=True)
y = data_copy['Reputations']

tfidf = TfidfVectorizer(min_df=4, max_df=.7)
x = tfidf.fit_transform(X['Comment_words'])
X_sparse = X.drop('Comment_words', axis=1).astype(pd.SparseDtype("int", np.nan))
X = hstack((x, X_sparse))

TfidVecがsparce matを返すため、get_dummiesで作られたデータもsprace matに変換する必要が(多分)あります
このへん、自分も怪しいのでもっといい方法があると思うので、調べときます。

あと、sprase matへの変換方法もastypeしかないのか?っていうのもあり、非常に微妙なコードですw

ま、気をとりなおして分析へ

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
nb = MultinomialNB()
nb.fit(X_train, y_train)

y_pred = nb.predict(X_test)
print(accuracy_score(y_true=y_test, y_pred=y_pred))
print(f1_score(y_true=y_test, y_pred=y_pred, average='macro'))

0.5714285714285714
0.1865531914893617

まずはTfidfと相性がいいMultinomialNBから。
KaggleのNLPのデータセットでこれ使っている人見て勉強しました。
多クラスかつ離散のときつかえます

とはいえ、rawデータはやはり精度も高くないすね〜〜
Tfidfのハイパーパラメータも少しいじりましたけど、そんなでした。

ラスト。

SVMでやってみます。
ま、本命ですかね?w

param_dst = {'C': np.arange(0.01, 10, 0.3), 'kernel': ['rbf', 'poly'], 'gamma': [0.01, 0.1]}

RS = RandomizedSearchCV(estimator=SVC(), param_distributions=param_dst, n_iter=50, cv=10, random_state=123)
RS.fit(X_train, y_train)
y_pred2 = RS.predict(X_test)

print('f1', f1_score(y_true=y_test, y_pred=y_pred, average='macro'))
print('acc', accuracy_score(y_true=y_test, y_pred=y_pred2))

f1 0.17315837339223075
acc 0.6134453781512605

お!ちょいあがりましたw
けど、頑張っても61%なので、むむむ〜〜って感じで、実践的なモデル構築とまではいってないですな。

#まとめ

今回はDockerによる環境構築をしてみたい!というところから始まり、データをスクレイピングで集めて多クラス分類までしてみました。
実際にはgensimのword2vecとかで文章要約とかはコード作ろうかな〜と思っているんで、その時はまた書きます。
あと、noteにいろんなコラム書いてたりするので、それをQiitaに移行しようかしら?とか思ってたりもします。
もし何かしらの役に立てれば幸いです!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?