他の 100 本ノックシリーズ

言語処理 100 本ノック(第 6 章: 機械学習)

50. データの入手・整形

News Aggregator Data Setをダウンロードし、以下の要領で学習データ(train.txt),検証データ(valid.txt),評価データ(test.txt)を作成せよ.

  1. ダウンロードした zip ファイルを解凍し,readme.txt の説明を読む.
  2. 情報源(publisher)が"Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail"の事例(記事)のみを抽出する.
  3. 抽出された事例をランダムに並び替える.
  4. 抽出された事例の 80%を学習データ,残りの 10%ずつを検証データと評価データに分割し,それぞれ train.txt,valid.txt,test.txt というファイル名で保存する.ファイルには,1行に1事例を書き出すこととし,カテゴリ名と記事見出しのタブ区切り形式とせよ(このファイルは後に問題 70 で再利用する).

学習データと評価データを作成したら,各カテゴリの事例数を確認せよ.

ダウンロードと解凍

$ curl -O https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip
$ unzip NewsAggregatorDataset.zip
$ cat readme.txt

メインコード

with open("newsCorpora.csv") as f:
    lines = f.readlines()

# 2. 記事の抽出
def filter_articles(lines):
    allow_publishers = ["Reuters", "Huffington Post", "Businessweek", "Contactmusic.com", "Daily Mail"]
    for line in lines:
        _, title, _, publisher, category, *_  = line.split("\t")
        if publisher in allow_publishers:
            yield "{}\t{}\n".format(category, title)

allow_articles = list(filter_articles(lines))
print(len(allow_articles)) #> 13356

# 3. 並び替え
import random
random.seed(0)
random.shuffle(allow_articles)

# 4. データの分割と書き出し
def write_articles(fname, lines):
    with open(fname, mode='x') as f:
        f.write("".join(lines))

len_allow_articles = len(allow_articles)
s1, s2 = int(len_allow_articles*0.8), int(len_allow_articles*0.9)
train, valid, test = allow_articles[:s1], allow_articles[s1:s2], allow_articles[s2:]

write_articles("train.txt", train)
write_articles("valid.txt", valid)
write_articles("test.txt", test)
! cat train.txt | wc -l # 10684
! cat valid.txt | wc -l # 1336
! cat test.txt | wc -l # 1336

memo

おそらく csv 形式の問題でpandasを使って読み込むと読み込み後の行数が変わる。

$ cat newsCorpora.csv | wc -l # 422937
import pandas as pd
df = pd.read_csv("newsCorpora.csv",sep="\t", header=None)
print(len(df.index)) #> 422419
検証
df = pd.read_csv("newsCorpora.csv",sep="\t", header=None, engine="python", error_bad_lines=False)
# エラー一部抽出
Skipping line 9180: '	' expected after '"'
Skipping line 10581: '	' expected after '"'
他の方法

cat newsCorpora.csv | awk -F '\t' '{print $4}' とかでうまく分けられると思う。

各カテゴリ事例数

echo "train.txt"; cut -f 1 train.txt | sort | uniq -c | sort -r; echo;
echo "valid.txt"; cut -f 1 valid.txt | sort | uniq -c | sort -r; echo;
echo "test.txt"; cut -f 1 test.txt | sort | uniq -c | sort -r; echo;
train.txt
4557 b
4180 e
1203 t
 744 m

valid.txt
 543 b
 542 e
 159 t
  92 m

test.txt
 572 e
 527 b
 163 t
  74 m


51. 特徴量抽出

学習データ,検証データ,評価データから特徴量を抽出し,それぞれ train.feature.txt,valid.feature.txt,test.feature.txt というファイル名で保存せよ. なお,カテゴリ分類に有用そうな特徴量は各自で自由に設計せよ.記事の見出しを単語列に変換したものが最低限のベースラインとなるであろう.

いまいち何をすれば良いのかわからないので以下の処理をしようと思う

  1. 記事見出しのトークン化
  2. クリーニングとフィルタリング
  3. レンマ化

中間ファイルの作成

import spacy

def generate_middle(fname1, fname2):
  with open (fname1) as f:
    lines = f.readlines()

  nlp = spacy.load("en_core_web_sm")
  text = ""
  for line in lines:
    category, topic = line.rstrip("\n").split("\t")
    doc = nlp(topic)
    tokens = [e.lemma_ for e in doc if e.pos_ not in ["PUNCT", "SYM", "NUM", "SPACE"] and not  e.is_stop]
    text += "{}\t{}\n".format(category, " ".join(tokens))

  with open (fname2, mode='x') as f:
    f.write(text)

generate_middle("train.txt", "train.middle.txt")
$ diff <(head -n 5 train.txt) <(head -n 5 train.middle.txt)
1,5c1,5
< t	White House Science Fair Will Focus On Girls In STEM
< b	US STOCKS-Wall St dips after Fed's Bullard talks about rates
< m	UPDATE 1-Some on downed Malaysian plane were heading to AIDS conference
< b	FOREX-Euro struggles after German data, nears 2-year low vs sterling
< b	Fitch Affirms Tula Region at 'BB'; Outlook Stable
---
> t	White House Science Fair focus girl STEM
> b	stock Wall St dip Fed Bullard talk rate
> m	UPDATE down malaysian plane head AIDS conference
> b	FOREX Euro struggle german datum near low vs sterling
> b	Fitch Affirms Tula Region BB Outlook Stable

train データを元に特徴量作成

from gensim import corpora

with open("train.middle.txt") as f:
  lines = f.readlines()

docs = [line.rstrip("\n").split("\t")[1] for line in lines]
tokens = [doc.split(" ") for doc in docs]

dct = corpora.Dictionary(tokens)
print(len(dct)) #> 13945

dct.filter_extremes(no_below=2)
print(len(dct)) #> 7469

dct.save_as_text('train.feature.dict.txt')

参考: https://tedboy.github.io/nlps/generated/generated/gensim.corpora.Dictionary.filter_extremes.html

参考: https://qiita.com/tatsuya-miyamoto/items/f505dfa8d5307f8c6e98

52. 学習

51 で構築した学習データを用いて,ロジスティック回帰モデルを学習せよ.

トレーニングデータ、検証データの読み込み

from itertools import chain

def read_f(path):
  with open(path) as f:
    lines = f.readlines()

  docs = [line.rstrip("\n").split("\t") for line in lines]
  flatten_docs = list(chain.from_iterable(docs))
  cats = flatten_docs[::2]
  texts = flatten_docs[1::2]

  return cats, texts

train_cats, train_texts = read_f("train.middle.txt")
valid_cats, valid_texts = read_f("valid.middle.txt")

学習データの作成

from gensim import corpora

def c_mat(texts, dct):
  tokens = [text.split(" ") for text in texts]
  corpus = [dct.doc2bow(token) for token in tokens]

  NUM = len(dct)
  def corpus_to_list(arr):
    ll = [0] * NUM
    for k, v in arr:
      ll[k] = v
    return ll

  return  [corpus_to_list(c) for c in corpus]

dct = corpora.Dictionary.load_from_text('train.feature.dict.txt')
train_X = c_mat(train_texts, dct)
valid_X = c_mat(valid_texts, dct)

cat_d = {"b" : 1, "e" : 2, "m" : 3, "t" : 4}
train_Y = [cat_d[e] if cat_d.get(e) else -1 for e in train_cats]
valid_Y = [cat_d[e] if cat_d.get(e) else -1 for e in valid_cats]

print(*map(len, [train_X, train_Y, valid_X, valid_Y])) #> 10684 10684 1336 1336

フィッティング

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000)
lr.fit(train_X, train_Y)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=1000,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)

参考: https://qiita.com/nymwa/items/774ca6c542c1eaff160d

53. 予測

52 で学習したロジスティック回帰モデルを用い,与えられた記事見出しからカテゴリとその予測確率を計算するプログラムを実装せよ.

train_Y_pred = lr.predict(train_X)
valid_Y_pred = lr.predict(valid_X)

train_Y_prob = list(map(max, lr.predict_proba(train_X)))
valid_Y_prob = list(map(max, lr.predict_proba(valid_X)))

予測カテゴリと確率の導出だけで許してください。

54. 正解率の計測

52 で学習したロジスティック回帰モデルの正解率を,学習データおよび評価データ上で計測せよ.

from sklearn.metrics import accuracy_score

print("訓練データ")
print('accuracy = ', accuracy_score(y_true=train_Y, y_pred=train_Y_pred))
print()
print("検証データ")
print('accuracy = ', accuracy_score(y_true=valid_Y, y_pred=valid_Y_pred))
訓練データ
accuracy =  0.9873642830400599

検証データ
accuracy =  0.8959580838323353

参考: https://qiita.com/0NE_shoT_/items/b702ab482466df6e5569