どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

Volume1

0100: Sale Result

解答例

0101: Aizu PR

解答例

0102: Matrix-like Computation

解答例

0103: Baseball Simulation

解答例

0100: Sale Result

販売実績

問題ページ

解き方

売上金額(data)と社員番号(order)を持つリストを用意する。

dataのindexが社員番号に対応している。例えば、社員番号が1001の場合dataは

data[1001] = 売上 となる。

orderには入力された順に社員番号を入れていく。

入力が終わると入力された順に社員番号を参照し売上が1,000,000以上の社員番号を出力する。

コード(python)

while True:
    n = int(input())
    if n == 0:
        break
    data = [0] * 4001
    order = []
    for _ in range(n):
        e, p, q = map(int, input().split())
        data[e] += p*q
        if e not in order:
            order.append(e)

    flag = True
    for e in order:
        if data[e] >= 1000000:
            print(e)
            flag = False
    if flag:
        print("NA")

2018年

2018年、今年1年が勝負の年。

今のこの状況から這い上がるためには努力するしかない。

年越しそば

今日は大晦日ということで、年越しそばを食べた。今まではどん兵衛などで済ましてきて自分では作ったことなかったが今回は初めて自分で作って食べてみた。

完成したのが下の写真。

f:id:pytry3g:20171231180300j:image

失敗した\(^o^)/

いろいろ失敗したので来年のためになぜ失敗したのか書き留めておく。

  1. 間違ってざる用のつゆを買ってしまった
  2. すりおろし器がなくて、じゃが芋や人参の皮をむくときに使う皮むき器をつかったので長芋がスライスになってしまった。
  3. ねぎを入れすぎた。

結果、あまりおいしくなかった。

あったかいそばを食べたかったのでつゆにお湯をいれたら味が薄くなるし、ネギを入れすぎたせいで最後のほうはネギばっかりになって、しかもけっこう苦かったし、、、

 

来年は今回の失敗を活かしてうまいそばを作りたい。

PyTorchを使ってSMSSpamCollectionの分類をしてみる。(2)

前回の続き、今回はTFIDFを使ってスパム分類をしてみる。

pytry3g.hatenablog.com

前準備

import argparse
import codecs
import string
import numpy as np
from nltk import word_tokenize
from sklearn.feature_extraction.text import TfidfVectorizer

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as O
from torch.autograd import Variable


class Tfidf(nn.Module):
    def __init__(self, n_in, n_hidden, n_out):
        super(Tfidf, self).__init__()
        self.l1 = nn.Linear(n_in, n_hidden)
        self.l2 = nn.Linear(n_hidden, n_hidden)
        self.l3 = nn.Linear(n_hidden, n_out)

    def forward(self, x):
        h = F.relu(self.l1(x))
        h = F.relu(self.l2(h))
        y = F.softmax(self.l3(h))
        return y

def tokenizer(text):
    words = word_tokenize(text)
    return words

parser = argparse.ArgumentParser()
parser.add_argument('--batchsize', '-b', type=int, default=50,
                    help="Number of minibatchsize...")
parser.add_argument('--epoch', '-e', type=int, default=100,
                    help="Number of epochs...")
parser.add_argument('--learning_rate', '-lr', type=float, default=0.1,
                    help="Learning rate...")
parser.add_argument('--units', '-u', type=int, default=100,
                    help="Number of units...")
args = parser.parse_args()

TFIDF

前回と同じようにデータをテキストとラベルに分けて正規化する。 前回と違う点はテキストデータをTFIDFで表現するところ。 scikit-learnを使えば簡単に実装できます。

# Create TF-IDF of text data
tfidf = TfidfVectorizer(tokenizer=tokenizer, stop_words="english",
                        max_features=1000)
sparse_tfidf = tfidf.fit_transform(text)
N, _ = sparse_tfidf.shape
# Split data into train set and test one
train_indices = np.random.choice(N, round(0.8*N), replace=False)
test_indices = np.array(list(set(range(N)) - set(train_indices)))
train_x = sparse_tfidf[train_indices]
train_t = np.array([x for i, x in enumerate(label) if i in train_indices])
test_x = sparse_tfidf[test_indices]
test_t = np.array([x for i, x in enumerate(label) if i in test_indices])

Training

permutationを使ってindexをシャッフルしミニバッチ学習する。

for epoch in range(epochs):
    if epoch % 10 == 0:
        print("Epoch {}".format(epoch))

    # Get random indices of train set
    indices = np.random.permutation([i for i in range(train_x.shape[0])])
    for i in range(0, len(indices), batchsize):
        start = i
        end = min(i+batchsize, len(indices))
        x = Variable(torch.FloatTensor([data.tolist()[0] for data in train_x[start:end].todense()]))
        t = Variable(torch.LongTensor(np.asarray(np.transpose(train_t[start:end]).astype(np.int64))))
        optimizer.zero_grad()
        y = model(x)
        loss = criterion(y, t)
        loss.backward()
        optimizer.step()
    history['loss'].append(loss.data[0])

結果

0.86

結果は0.86となりました。tfidfのほうがいい結果になると思ったが、前回より若干悪くなってしまった。 原因はよくわからない。

コード

コードを一応あげておきます。

tfidf.py

github.com

UbuntuでMeCabを使ってみる

前回は最低限の設定をしたので今回はMeCabを使えるように設定していく。

pytry3g.hatenablog.com

 

MeCab

MeCabとは形態素解析(テキストから単語を切り出して、単語の役割を解析する技術)をするためのツールです。テキストを分析や処理を行うにはまずテキストを単語に分割します。日本語のような分かち書き(単語の区切りに空白がある書き方)がされていない言語は何らかの方法により分かち書きする必要があります。そこで使われるのが形態素解析です。

例えば、”今日はいい天気ですね。”というテキストをMeCabを使って形態素解析してみると以下のような結果になります。形態素解析後が分かち書きしたものになります。

形態素解析前ー> 今日はいい天気ですね。

形態素解析後ー> 今日 は いい 天気 です ね 。 

 

インストール

MeCabのインストールはターミナルを開いて、以下の3つでコマンドラインpythonを使ったプログラムから使えるようになります。

  1. sudo apt install aptitude
  2. sudo aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file
  3. pip install mecab-python3

 

コマンドライン

ここではコマンドラインMeCabを使ってみます。

ターミナルを開いてmecabコマンドを実行して日本語のテキストを入力すると形態素解析の結果を見ることができます。

$ mecab
今日はいい天気ですね。
今日	名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
いい	形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
天気	名詞,一般,*,*,*,*,天気,テンキ,テンキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね	助詞,終助詞,*,*,*,*,ね,ネ,ネ
。	記号,句点,*,*,*,*,。,。,。
EOS

-O wakatiオプションにより単語の切り出しのみもできる。

$ mecab -O wakati
今日はいい天気ですね。
今日 は いい 天気 です ね 。

次のコマンドを実行すると、

mecab -O wakati -o <output file> <input file>

テキストが書かれたファイルを読み込んで、分かち書きされた結果をファイルに書き込むことができる。

例えば、次のようなファイルがあった場合、

input.txt

今日はいい天気ですね。
めっちゃ走ったのに、電車に間に合わなかった。

次のように実行すると

mecab -O wakati -o output.txt input.txt

output.txt

今日 は いい 天気 です ね 。
めっちゃ 走っ た のに 、 電車 に 間に合わ なかっ た 。

このようなファイルが作られる。

プログラム

ここではpythonを使ってMeCabを使ってみます。

MeCabインスタンスを作る際にオプションを"-Owakati"とすると分かち書き、"-Ochasen"とすると単語の詳細な情報をみることができます。

分かち書き

import MeCab
text = "今日はいい天気ですね。"
tagger = MeCab.Tagger("-Owakati")
wakati = tagger.parse(text) #テキストを分解する。
print(wakati) #今日 は いい 天気 です ね 。

詳細

import MeCab
text = "今日はいい天気ですね。"
tagger = MeCab.Tagger("-Ochasen")
tagger.parse('') #これがないとエラーがでるかもしれない
tag = tagger.parseToNode(text)
while tag:
# tag.surface -> 単語
# tag.feature -> 詳細な情報 print(tag.surface, tag.feature) tag = tag.next
"""結果
BOS/EOS,*,*,*,*,*,*,*,*
今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
いい 形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね 助詞,終助詞,*,*,*,*,ね,ネ,ネ
。 記号,句点,*,*,*,*,。,。,。
BOS/EOS,*,*,*,*,*,*,*,*

"""

詳細な情報を使えばテキストから名詞のみをとりだすことができる。

while tag:
   features = tag.feature.split(',')
   if features[0] == "名詞":
       print(tag.surface, tag.feature)
   tag = tag.next
   """結果
   今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
   天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ
   """

PyTorchを使ってSMSSpamCollectionの分類をしてみる。

環境

  • python 3.6.2
  • Anaconda 4.3.27
  • Windows10
  • scikit-learn 0.19.0
  • nltk 3.2.4
  • pytorch 0.2.1

前準備

import argparse
import codecs
import string
from collections import Counter
from nltk import word_tokenize
from nltk.corpus import stopwords
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as O
from torch.autograd import Variable


parser = argparse.ArgumentParser()
parser.add_argument('--batchsize', '-b', type=int, default=50,
                    help="Number of minibatchsize...")
parser.add_argument('--epoch', '-e', type=int, default=100,
                    help="Number of epochs...")
parser.add_argument('--learning_rate', '-lr', type=float, default=0.1,
                    help="Learning rate...")
parser.add_argument('--units', '-u', type=int, default=100,
                    help="Number of units...")
args = parser.parse_args()

データのダウンロード

今回使うデータはカリフォルニア大学アーバイン校が提供しているMachine Learning RepositoryのSMS Spam Collectionです。

https://archive.ics.uci.edu/ml/machine-learning-databases/00228/

からsmsspamcollection.zipをダウンロード。
smsspamcollection.zipを解凍すると、SMSSpamCollectionファイルがあるのでこれを使っていきます。

データの中身

SMSSpamCollectionの中身は以下のようになっています。

行の先頭にラベル(hamかspam)、それにそのラベルのテキストデータ(下線)がタブ区切りで続いています。

ham Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...

ham Ok lar... Joking wif u oni...

spam Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's

ham U dun say so early hor... U c already then say...

ham Nah I don't think he goes to usf, he lives around here though

spam FreeMsg Hey there darling it's been 3 week's now and no word back! I'd like some fun you up for it still? Tb ok! XxX std chgs to send, £1.50 to rcv

全体のデータのサイズは5574。

hamデータは4827。

spamデータは747。

with codecs.open("SMSSpamCollection", "r", "utf-8") as f:
    data = f.read().splitlines()
N = len(data) #5574
ham_size = sum(d.split('\t')[0] == "ham" for d in data) #4827
spam_size = N - ham_size #747
print("Number of data: {}\nNumber of ham data: {}\nNumber of spam data: {}".format(N, ham_size, spam_size))

データの前処理

データの分割

手に入れたデータをテキストデータとラベルデータに分ける。 spamのラベルは1に、hamは0にする。

text = [d.split('\t')[1] for d in data]
label = [[0, 1][d.split('\t')[0]=="spam"] for d in data]

正規化

textから句読点を除く。

text = ["".join(ch for ch in line if ch not in string.punctuation) for line in text]

textを全て小文字に変換する。

text = [line.lower() for line in text]

ストップワードとテキストデータに3回以上出現しない単語を取り除く

#Stopword List
stopwords_list = set(stopwords.words('english'))
#Word dictionary which contains the word appearing more than 3 times in dataset
word2id = {"<UNK>": 0}
#Remove stopwords
word_list = set([word for line in text \
                for word in word_tokenize(line) if word not in stopwords_list])

cnt = Counter()
#Count all word appear in dataset
for line in text:
    for word in line.split():
        if word in word_list:
            cnt[word] += 1

for word in word_list:
    if word not in word2id and cnt[word] >= 3:
        word2id[word] = len(word2id)

Bag of Words

今回はBoWを使う。 正規化したテキストデータをBoWに変換する。

bow_set = []
for line in text:
    bow = [0] * len(word2id)
    for word in line.split():
        try:
            bow[word2id[word]] += 1
        except:
            pass
    bow_set.append(bow)

データの分割

訓練データとテストデータに分ける。

train_x, test_x, train_t, test_t = train_test_split(bow_set, label, test_size=0.1)

Training

ネットワークの定義

入力層、隠れ層、出力層からなるニューラルネットワーク

class Bag_Of_Words(nn.Module):
    def __init__(self, n_in, n_hidden, n_out):
        super(Bag_Of_Words, self).__init__()
        self.l1 = nn.Linear(n_in, n_hidden)
        self.l2 = nn.Linear(n_hidden, n_hidden)
        self.l3 = nn.Linear(n_hidden, n_out)

    def forward(self, x):
        h = F.relu(self.l1(x))
        h = F.relu(self.l2(h))
        y = F.softmax(self.l3(h))
        return y

パラメータの設定

batchsize = args.batchsize
epochs = args.epoch
learning_rate = args.learning_rate
n_in = len(word2id)
n_hidden = args.units
n_out = 2
n_batch = len(train_x) // batchsize
history = {"loss": []}

#モデルの構築
model = Bag_Of_Words(n_in, n_hidden, n_out)
#損失関数
criterion = nn.CrossEntropyLoss()
#最適化関数
optimizer = O.Adam(model.parameters(), lr=learning_rate)

学習

for epoch in range(epochs):
    print("Epoch {}".format(epoch))
    train_x, train_t = shuffle(train_x, train_t)
    for i in range(n_batch):
        # Get mini batch data...
        start = i * batchsize
        end = start + batchsize
        x = Variable(torch.FloatTensor(train_x[start:end]))
        t = Variable(torch.LongTensor(train_t[start:end]))
        optimizer.zero_grad()
        y = model(x)
        loss = criterion(y, t)
        loss.backward()
        optimizer.step()
    history['loss'].append(loss.data[0])

Test

以下のコードで学習したモデルにテストデータを渡し、正解率を確認する。

var = Variable(torch.FloatTensor(test_x), requires_grad=False)
result = model(var)
predicted = torch.max(result, 1)[1]
print("{:.2f}".format(sum(p == t for p, t in zip(predicted.data, test_t)) / len(test_t)))

結果

0.92

結果は0.92となりました。何回か学習しテストしてみましたが大体9割前後の正答率でした。

コード

コードを一応あげておきます。

github.com