mecab
機械学習
scikit-learn
python3

ディープラーニングを用いて社説から新聞社を予測する

概要

機械学習の勉強も兼ねて
各新聞社の社説を形態素解析により分解しニューラルネットを用いて学習することでどこの新聞社っぽい文章か判別するモデルを作りました。

プログラム

1.text_make.py  :  社説一覧サイトから社説のテキストファイルを作成する
2.csv_make.py  :  1でできたテキストファイルからcsvファイルを作成する
3.nlp_tasks.py  :  MeCabを用いて文章の分かち書きを行う
4.main.py    :  ニューラルネットによる学習を行う
3.4.は以下のサイトを参考にしほぼそのままです。
https://spjai.com/category-classification/

テキストファイルの作成

text_make.pyの説明
このプログラムでは各社の社説がストックされているサイトhttp://editorial.x-winz.net/
からテキストファイルの作成を行いました。

text_make.py
import requests,bs4,os
"""現在のディレクトリの取得"""
current_dir=os.getcwd()




"""記事本文の抜出"""

def body(url):
    res= requests.get(url)

    soup=bs4.BeautifulSoup(res.text,"html.parser")
    elems=soup.select('.entry')
    for elem in elems:
        return elem.getText()
"""テキストファイルの作成"""

def write(num,company,url):

    f = open(company+str(num)+'.txt', 'w') # 書き込みモードで開く
    f.write(body(url)) # 引数の文字列をファイルに書き込む
    f.close() 

"""社名のフォルダを作成しフォルダ内に社説を保存していく"""
def syasetu_text(company):
    os.mkdir(company)
    os.chdir("./"+company) # pythonディレクトリに移動

    for page in range(1,69):

        res_list= requests.get('http://editorial.x-winz.net/ed-category/editorial/national/'+company+'/page/'+str(page))


        soup_list=bs4.BeautifulSoup(res_list.text,"html.parser")
        elems_list=soup_list.select('.post-box-title a')
        #print(elems_list)
        list_url=[]
        for elem in elems_list:
            list_url.append(elem.get('href'))

        for i in range(len(list_url)):

            write(i+(page-1)*10,company,list_url[i])#普通

                        print(company+str(i+(page-1)*10))
    os.chdir(current_dir)


syasetu_text('nikkei') 
syasetu_text('sankei')
#syasetu_text('asahi')    
syasetu_text('yomiuri') 
syasetu_text('mainichi') 

rangeで何ページまで取得するか設定します。今回はだいたい一年分の68ページまで習得しています。
朝日新聞のみ上記のサイトではストックが少なかったためhttp://shasetsu.seesaa.net/
のサイトを用い以下のプログラムでテキストファイルの作成を行いました。

asahi_make.py
import requests,bs4

def write(num,text):
    os.mkdir('asahi')
    os.chdir("./asahi")


    f = open('asahi'+str(num)+'.txt', 'w') # 書き込みモードで開く
    f.write(text) # 引数の文字列をファイルに書き込む
    f.close() 

    for page in range(1,38):
        res= requests.get    ('http://shasetsu.seesaa.net/category/4848832-'+str(page)+'.html')

        soup=bs4.BeautifulSoup(res.text,"html.parser")
        elems=soup.select('.text')
        list_text=[]
        for elem in elems:
            list_text.append(elem.getText())

    for i in range(1,len(list_text)):

        write(i+(page-1)*19,list_text[i])

CSVファイルの作成

得られたテキストファイルからCSVファイルの作成を行います。

asahi_make.py
import os
import csv
"""現在のディレクトリの取得"""

current_dir=os.getcwd()

"""フォルダ内のファイルの数を取得する"""
def GetFileNum(sInFdr):
    if not os.path.isdir(sInFdr): return 0
    i=0
    for root, dirs, files in os.walk(sInFdr):
        i+=len(files)
    return i


datas=[]
f = open('data_test.csv', 'w')
csv_writer = csv.writer(f,quotechar="'")
"""CSVファイルの作成"""
def make_csv(list_company):
    for i in range(len(list_company)):#3398#2718
        os.chdir(current_dir)
        file_num=GetFileNum(list_company[i])

        os.chdir("./"+list_company[i]) # 各新聞社のディレクトリに移動

        for j in range(file_num):

            r = open(list_company[i]+str(j)+'.txt', 'r')
            line_a = r.readlines()



            """朝日新聞のみテキストの形式が異なるため注意"""
            if(list_company[i]=='asahi'):
                text = ''
                for line in line_a[0:]:
                    text += line.strip()


            else:
                text = ''
                for line in line_a[2:]:
                    text += line.strip()

            datas.append([text,list_company[i]])
list_company=['asahi','mainichi', 'nikkei', 'sankei', 'yomiuri']
make_csv(list_company)
csv_writer.writerows(datas)
f.close()

朝日新聞のみ別サイトからとってきているためテキストの形式が異なることに注意してください。

形態素解析

文章を文章のままで分析することは難しいのである程度の部分列で区切る必要がありますが文章を区切る上で英語と比べ日本語には決定的な弱点があります。それは単語の区切りがわかりにくいことです。
英語であれば単語同士が半角スペースで区切られているため文の構成要素がすぐにわかりますが日本語ではすぐに構成要素はわかりません。
よく上げられる例として「すもももももももものうち」の文の区切り方を見つけいるのは難解です。
そこで形態素解析という手法を用います。
「すもももももももものうち」という文であれば
→すもも(名詞,一般)/も(助詞,係助詞)/もも(名詞,一般)/も(助詞,係助詞)/
 もも(名詞,一般)/の(助詞,連体化)/うち(名詞,非自立)

のように分割することで文章を解析できるようにします。
分割の方法として、MeCabという形態素解析のライブラリを用いることで
形態素解析を行うことができます。

bag of words

bag of wordsとは上記のように形態素解析したものを文のつながりを考えずにすべてバッグに詰め込んでしまうイメージです。

形態素解析.png
このようにして出現する全ての単語を把握したあと、全ての単語に対してテキスト中に出現するかどうかというベクトルを作成します。
それを入力データとしてニューラルネットワークで学習します。

社説の学習と推定

modelsというフォルダを作成します。
下記のプログラムで文章の分かち書きを行い配列を作成します。

nlp_tasks.py
import MeCab
from sklearn.feature_extraction.text import CountVectorizer

def _split_to_words(text):
    tagger = MeCab.Tagger('-O wakati')
    try:
            res = tagger.parse(text.strip())
    except:
            return []
    return res



def get_vector_by_text_list(_items):
    count_vect = CountVectorizer(analyzer=_split_to_words)
    bow = count_vect.fit_transform(_items)
    X = bow.todense()
    return [X,count_vect]

main.pyにより学習と予測を行います。

main.py
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.externals import joblib
import os.path
import nlp_tasks
from sklearn.model_selection import cross_val_score


from sklearn.neural_network import MLPClassifier  # アルゴリズムとしてmlpを使用

def train():
    classifier = MyMLPClassifier()
    classifier.train('data_syasetu.csv')

def predict():
    classifier = MyMLPClassifier()
    classifier.load_model()
    d=open('judge_text.txt',"r")
    reader=d.read()

    d.close

    result = classifier.predict(reader)
    print(result)
    """
    f=open('result.txt','w')
    f.write(result)
    """


class MyMLPClassifier():
    model = None
    model_name = "mlp"

    def load_model(self):
        if os.path.exists(self.get_model_path())==False:
            raise Exception('no model file found!')
        self.model = joblib.load(self.get_model_path())
        self.classes =  joblib.load(self.get_model_path('class')).tolist()
        self.vectorizer = joblib.load(self.get_model_path('vect'))
        self.le = joblib.load(self.get_model_path('le'))

    def get_model_path(self,type='model'):
        return 'models/'+self.model_name+"_"+type+'.pkl'

    def get_vector(self,text):
        return self.vectorizer.transform([text])



    def train(self, csvfile):
        df = pd.read_csv(csvfile,names=('text','category'),encoding='shift_jis')

        X, vectorizer = nlp_tasks.get_vector_by_text_list(df["text"])

        # loading labels
        le = LabelEncoder()
        le.fit(df['category'])
        Y = le.transform(df['category'])

        model = MLPClassifier(max_iter=150, hidden_layer_sizes=(150,),verbose=10,)
        model.fit(X, Y)

    # save models
        joblib.dump(model, self.get_model_path())
        joblib.dump(le.classes_, self.get_model_path("class"))
        joblib.dump(vectorizer, self.get_model_path("vect"))
        joblib.dump(le, self.get_model_path("le"))

        self.model = model
        self.classes = le.classes_.tolist()
        self.vectorizer = vectorizer

    def predict(self,query):
        X = self.vectorizer.transform([query])

        key = self.model.predict(X)

        return self.classes[key[0]]
    def cross_validation(self,csvfile):
        self.model = MLPClassifier(max_iter=150, hidden_layer_sizes=(150,),verbose=10,)
        df = pd.read_csv(csvfile,names=('text','category'),encoding='shift_jis')
        _items = df["text"]

        X, vectorizer = nlp_tasks.get_vector_by_text_list(_items)

       # loading labels
        le = LabelEncoder()
        le.fit(df['category'])
        Y = le.transform(df['category'])
        scores = cross_val_score(self.model, X, Y, cv=4)
        print(scores)
        print(np.average(scores))


if __name__ == '__main__':
    #train()
    predict()
    """
    classifier = MyMLPClassifier()
    classifier.cross_validation('data_syasetu.csv')
    """

日経新聞で中国人の名前が使われていると文字コードエラーが出てしまったのでエラーが出た場合その部分を削除する必要があります。
predictをコメントアウトしtrainで学習することができます。
judge_text.txtというテキストファイルを作成しそこに判定したい文を書き込み
trainをコメントアウトしpredictを実行することで社名を予測することができます。
結果はresult.txtにも出力されます。

まとめと今後の課題

交差検証の結果として平均して8割の正答率であった。
文字コードでエラーが出そうなページは前もって間引くようなプログラムを作る必要がある。
より精度を高めるためにパラメータチューニングを行っていくことや学習データをもっと増やす必要がある。

おまけ

しんぶん赤旗の直近10日の社説を予測したところ7日分は朝日,三日分は毎日であった。両社ともどちらかというと左寄りの新聞であり左寄りか右寄りかで言葉選びにも違いがあるのかもしれない。