Tweet-Classifierを作ったよ その2~学習・検証~

こんにちは、takeshiho0531です。


takeshiho0531.hatenablog.com

これの続きの記事です。
Tweet-Classifierとはなんぞやいという方は↑の初めの何行かを読んでいただければという感じです。
この記事では、集めたツイートを「バズ」「炎上」「その他」にカテゴライズして、これからそれを学習させようという記事です。
このパートは『つくりながら学ぶ!PyTorchによる発展ディープラーニング』と、その著者である小川雄太郎氏がご自身のgithubで公開されている、
github.com
を真似してやってみました。

. . . . . . . . . . . . . . . . . . . . .

元々は『つくりながら学ぶ!PyTorchによる発展ディープラーニング』にあった、BERTを使ってIMDbの感情分析モデルを構築するという部分を自分の母語である日本語でやってみたいと思い、このTweet-Classifierを作ってみようと思った感じでした。
ただこの本で学習した時点でtorchtextの新しいバージョンではtorchtext.data.Fieldが使えなくなっていて、いろいろ環境構築でグズグズした結果、ローカルでは仮想環境ができたものの、AWSGPUインスタンスでは元々入っているPyTorchのバージョンを再インストールして変更することがなぜかできなくて、GPUインスタンスで学習させるというところまで到達できずにいました。(AWSGPUインスタンスがどうこうという話は 『PyTorchによる発展ディープラーニング』1-4 AWSのAMIの選び方 - takeshiho0531のブログ に書きました。)

. . . . . . . . . . . . . . . . . . . . .

今回、Dockerの使い方を学んだので、元々condaとかPyTorchとかが入っているものではなくて、普通のubuntuサーバーのモデルを選んでやってみようと思いました。AWS Marketplace: Ubuntu Server 20.04 LTS (HVM), SSD Volume with Support by Terracloudxのp2.xlargeを選びました。このセクションの以下の内容は今まで一度もやったことがないことだったので一つ一つのステップを調べながら失敗しながら進めていきましたが、すごく精神的に疲弊しました。


まずNvidia Driverのインストールですが、ドキュメントをそのままコピペしてインスタンスにインストールしました。
docs.nvidia.com
ただこの通りにやったのですが、nvidia-smiと打っても

"NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running"

と出てしまいました。エラーメッセージの意味もわからなかったので、ここはググって
jskangaroo.hatenablog.com
を参考にすると、このブログを書いた方と同じタイミングでnvidia-smiに反応してくれるようになりました。


次にNvidia Dockerのインストールですが、github(GitHub - NVIDIA/nvidia-docker: Build and run Docker containers leveraging NVIDIA GPUs)に説明があり、そこにリンクがあった
docs.nvidia.com
を見ながらインストールしました。


その後、自分のgithubレポジトリをインスタンスにクローンする形でDockerfileをインスタンス内に入れて、そこからDocker imageをbuildし、コンテナを立てました。学習はJupyter notebookを使いながらやろうかなと思っていたので、Dockerfileは↓みたいな感じです。(githubにも上げていますが。)

FROM nvidia/cuda:11.3.0-cudnn8-runtime-ubuntu20.04
RUN apt-get update && apt-get install -y \
		sudo \
		wget \
		vim
WORKDIR /opt
RUN wget https://repo.anaconda.com/archive/Anaconda3-2021.11-Linux-x86_64.sh && \
	bash Anaconda3-2021.11-Linux-x86_64.sh -b -p /opt/anaconda3 && \
	rm -f Anaconda3-2021.11-Linux-x86_64.sh

ENV PATH /opt/anaconda3/bin:$PATH

RUN pip install --upgrade pip && pip install \
	torch==1.8.1 \
	torchtext==0.9.1 \
	transformers[ja] \
	unidic-lite

WORKDIR /

CMD ["jupyter","lab","--ip=0.0.0.0","--allow-root","--LabApp.token=''"]

cudaのバージョンとかは、使いたいtorchtextのバージョンがある程度決まっていたので、Previous PyTorch Versions | PyTorchを見て、元となるDocker imageを決めました。どのanacondaをインストールするかは、Old package lists — Anaconda documentation を見ながら使いたいバージョンのtorchtextがあるものを選びました。


いざコンテナを立ててブラウザでJupyter notebookを開こうと思ったのですが、できませんでした。コンテナとインスタンスの通信はできてるみたいでしたが、ローカルとインスタンスでは繋がっていない??みたいな感じでできませんでした。確か "Connection refused"と言われた気がします。(ログとってませんでした.....)


ここでJupyter notebookをやめて普通にpythonファイルにしようかとも思ったのですが、イライラしていてかなり疲弊していたせいか、うまくいくとわかっているipynbファイルがもうできていたので書き換える気力が全くなくなってしまっていて(ほとんどコピペなのに??)やりませんでした。(多分ログを出すようにするのが面倒だと感じたんだと思います。)ものすごく疲れてしまっていたので、嫌になってインスタンスもさっさと消してしまいました。


. . . . . . . . . . . . . . . . . . . . .

だいぶん精神的に追い詰められてしまっていたので、別のマシンイメージを探し始めました。今まではクイックスタートAMIかAWS Marketplace AMIにしか手を出したことがなかったのですが、古いバージョンのubuntuとかはコミュニティAMIに移されているんだということに気づきました。そこで、Deep Learning AMI GPU PyTorch 1.9.0 (Ubuntu 20.04) 20211007 というAMIを選びました。このタイプならNvidia DriverとかNvidia Dockerとか考えなくても良いし、conda環境で欲しいバージョンのtorchtextも入れられるバージョンで、何よりも使ったことがある感じのものなので、学びという観点では△ですが課題を遂行しないといけないのですがる思いで選びました。いつも通りという感じで問題なく使えました。

ただ、学習にはすごく時間がかかるのですが、すぐにsshの通信が切れてしまい学習もそこで終わってしまうという問題点がありました。この点は
qiita.com
を見ながら10時間接続できるようにしました。(実際は5時間で終わりましたが。)

. . . . . . . . . . . . . . . . . . . . .

では学習とは一体何をしたのかというのを書きます。
上でも書きましたが、『つくりながら学ぶ!PyTorchによる発展ディープラーニング』にあった、BERTを使ってIMDbの感情分析モデルを構築を日本語でもやりたいと思って始めました。
日本語版のBERTは東北大学の出しているBERTを使いました。HuggingfaceのTransformersから簡単に使うことができます。
東北大BERTの出力を最後3つ(バズ・炎上・その他)にする層を接続させて、自前のTwitterのデータを用いてファインチューニングしました。
Attentionの結果は、BertLayerの最終モジュールのSelf-Attentionの結果を取ってくることにしました。

コードは
github.com
qiita.com
の二つを主に参考・真似させてもらいました。上の方は『つくりながら学ぶ!PyTorchによる発展ディープラーニング』の著者のgithubにあったのをたまたま見つけて真似させてもらったのですが、Attentionの可視化部分がなかったので下の方も参考にさせていただきました。Attentionの可視化部分に関してはHuggingfaceのドキュメントも使いました。
huggingface.co
一応以下のようなコードで学習させました。

# 乱数シードの固定
import os
import random
import numpy as np
import torch
import pandas as pd

SEED_VALUE = 1234  # これはなんでも良い
os.environ['PYTHONHASHSEED'] = str(SEED_VALUE)
random.seed(SEED_VALUE)
np.random.seed(SEED_VALUE)
torch.manual_seed(SEED_VALUE)  # PyTorchを使う場合
# GPUの使用確認:True or False
torch.cuda.is_available()
import torch
import torchtext 
# transformers等のライブラリが'./.local/lib/python3.8/site-packages'にインストールされたので。
import sys 
sys.path.append('./.local/lib/python3.8/site-packages')
import transformers
from transformers import BertModel
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
# データを読み込んだときに、読み込んだ内容に対して行う処理

max_length = 512  # 東北大学_日本語版の最大の単語数(サブワード数)は512


def tokenizer_512(input_text):
    """torchtextのtokenizerとして扱えるように、512単語のpytorchでのencodeを定義。ここで[0]を指定し忘れないように"""
    return tokenizer.encode(input_text, max_length=512, return_tensors='pt')[0]


TEXT = torchtext.legacy.data.Field(sequential=True, tokenize=tokenizer_512, use_vocab=False, lower=False,
                            include_lengths=True, batch_first=True, fix_length=max_length, pad_token=0)

LABEL = torchtext.legacy.data.Field(sequential=False, use_vocab=False)
# dataset
dataset_train_eval, dataset_test = torchtext.legacy.data.TabularDataset.splits(
    path='.', train='train_eval.tsv', test='test.tsv', format='tsv', fields=[('Text', TEXT), ('Label', LABEL)])

# torchtext.data.Datasetのsplit関数で訓練データと検証データを分ける

dataset_train, dataset_eval = dataset_train_eval.split(
    split_ratio=1.0 - 3401/13606, random_state=random.seed(1234))
# dataloader
batch_size = 16  

dl_train = torchtext.legacy.data.Iterator(
    dataset_train, batch_size=batch_size, train=True)

dl_eval = torchtext.legacy.data.Iterator(
    dataset_eval, batch_size=batch_size, train=False, sort=False)

dl_test = torchtext.legacy.data.Iterator(
    dataset_test, batch_size=batch_size, train=False, sort=False)

# 辞書オブジェクトにまとめる
dataloaders_dict = {"train": dl_train, "val": dl_eval}
# BERTモデル
from transformers import BertModel
model= BertModel.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', output_attentions=True)
from torch import nn

class BertForTweetClassifier(nn.Module):
    '''BERTモデルに3クラスを判定する部分をつなげたモデル'''

    def __init__(self):
        super(BertForLivedoor, self).__init__()

        # BERTモジュール
        self.bert = model  # 日本語学習済みのBERTモデル

        # headにポジネガ予測を追加
        # 入力はBERTの出力特徴量の次元768、出力は3クラス
        self.cls = nn.Linear(in_features=768, out_features=3)

        # 重み初期化処理
        nn.init.normal_(self.cls.weight, std=0.02)
        nn.init.normal_(self.cls.bias, 0)

    def forward(self, input_ids):
        '''
        input_ids: [batch_size, sequence_length]の文章の単語IDの羅列
        '''

        # BERTの基本モデル部分の順伝搬
        # 順伝搬させる
        result = self.bert(input_ids)  # reult は、sequence_output, pooled_output
        attentions = result['attentions']

        # sequence_outputの先頭の単語ベクトルを抜き出す
        vec_0 = result[0]  # 最初の0がsequence_outputを示す
        vec_0 = vec_0[:, 0, :]  # 全バッチ。先頭0番目の単語の全768要素
        vec_0 = vec_0.view(-1, 768)  # sizeを[batch_size, hidden_size]に変換
        output = self.cls(vec_0)  # 全結合層

        return output, attentions
# モデル構築
net = BertForTweetClassifier()

# 訓練モードに設定
net.train()
# ファインチューニングの設定
# 勾配計算を最後のBertLayerモジュールと追加した分類アダプターのみ実行

# 1. まず全部を、勾配計算Falseにしてしまう
for param in net.parameters():
    param.requires_grad = False

# 2. BertLayerモジュールの最後を勾配計算ありに変更
for param in net.bert.encoder.layer[-1].parameters():
    param.requires_grad = True

# 3. 識別器を勾配計算ありに変更
for param in net.cls.parameters():
    param.requires_grad = True

# 最適化手法の設定
import torch.optim as optim


# BERTの元の部分はファインチューニング
optimizer = optim.Adam([
    {'params': net.bert.encoder.layer[-1].parameters(), 'lr': 5e-5},
    {'params': net.cls.parameters(), 'lr': 1e-4}
])

# 損失関数の設定
criterion = nn.CrossEntropyLoss()
# モデルを学習させる関数を作成


def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):

    # GPUが使えるかを確認
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("使用デバイス:", device)
    print('-----start-------')

    # ネットワークをGPUへ
    net.to(device)

    # ネットワークがある程度固定であれば、高速化させる
    torch.backends.cudnn.benchmark = True

    # ミニバッチのサイズ
    batch_size = dataloaders_dict["train"].batch_size

    # epochのループ
    for epoch in range(num_epochs):
        # epochごとの訓練と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train()  # モデルを訓練モードに
            else:
                net.eval()   # モデルを検証モードに

            epoch_loss = 0.0  # epochの損失和
            epoch_corrects = 0  # epochの正解数
            iteration = 1
            logs=[]

            # データローダーからミニバッチを取り出すループ
            for batch in (dataloaders_dict[phase]):
                # batchはTextとLableの辞書型変数

                # GPUが使えるならGPUにデータを送る
                inputs = batch.Text[0].to(device)  # 文章
                labels = batch.Label.to(device)  # ラベル

                # optimizerを初期化
                optimizer.zero_grad()

                # 順伝搬(forward)計算
                with torch.set_grad_enabled(phase == 'train'):

                    # BERTに入力
                    outputs, attentions = net(inputs)

                    loss = criterion(outputs, labels)  # 損失を計算

                    _, preds = torch.max(outputs, 1)  # ラベルを予測

                    # 訓練時はバックプロパゲーション
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                        if (iteration % 10 == 0):  # 10iterに1度、lossを表示
                            acc = (torch.sum(preds == labels.data)
                                   ).double()/batch_size
                            print('イテレーション {} || Loss: {:.4f} || 10iter. || 本イテレーションの正解率:{}'.format(
                                iteration, loss.item(),  acc))

                    iteration += 1

                    # 損失と正解数の合計を更新
                    epoch_loss += loss.item() * batch_size
                    epoch_corrects += torch.sum(preds == labels.data)

            # epochごとのlossと正解率
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double(
            ) / len(dataloaders_dict[phase].dataset)

            print('Epoch {}/{} | {:^5} |  Loss: {:.4f} Acc: {:.4f}'.format(epoch+1, num_epochs,
                                                                           phase, epoch_loss, epoch_acc))
        #logを保存
        log_epoch={"epoch":epoch+1,"Loss":epoch_loss, "Acc":epoch_acc}
        logs.append(log_epoch)
        df=pd.DataFrame(logs)
        df.to_csv("log_output.csv")
            
       
        torch.save(net.state_dict(), "./save_model/model_"+str(epoch+1)+".pth")

    return net
num_epochs = 20
train_model(net, dataloaders_dict,
                          criterion, optimizer, num_epochs=num_epochs)

最後のepochの学習状況が以下です。

ファインチューニング学習状況

結果です。

ファインチューニング結果

正解率がなんか全然初めのepochから上がってなくて最終的にも0.6くらいなのがすごくすごく不穏でしたが、何が原因だったのでしょう....とりあえず課題遂行のために見なかったことにしましたが大丈夫ではないだろというね。


. . . . . . . . . . . . . . . . . . . . .

「2. 学習・検証」の部分での成長ポイントは、Nvidia Driver・Nvidia Dockerのインストールをしてみたことを使ってみたこと、Huggingfaceを使ってみたこと、何よりもやってみたかった日本語でのモデル構築ができたことかなと思っています。(ファインチューニング済みパラメータを得られたこと課題の進捗を埋めたことでだったかもしれないが。)改善ポイントは、なぜJupyter Notebookが開けなかったのかという部分と、Jupyter Notebook使わずにpythonファイルを実行させてみればよかったのでは?というところと、なぜ正解率が上がらなかったのかという部分かなと思っています。
まあでも精神的にすごく疲弊してしまった中頑張ったなと思います。


この記事はこれくらいにしようかな。「3. UI周り」に関しては次の記事にしようと思います。長くなってしまってごめんなさい。


ではでは〜👋👋

Tweet-Classifierを作ったよ その1 ~ツイートの収集・分析~

意味不明なタイトルになってますが、Tweet-Classifierという謎サービスを作りました。

概要としては「ツイート内容を入力するとそのツイートが、バズる・炎上する・その他 のどれになるかを 予測するサービスです」ということらしいです。

Aセメスターのプログラミング基礎という授業の最終課題(自由製作)として提出したものです。

コードはgithubにあげています。
github.com

デモ動画に関してはyoutubeにもあげておきました。
www.youtube.com


作るのがなかなか大変だったりしたので、その時のことを残しておこうと思ってブログにしています。成果発表会では上手くいった風に発表しましたが、実際はほとんど失敗に終わっていて、ダメだった点もここでは正直に記録して、後々成長してから見返せるようにしておきたいと思っています。(だからあまり誰かのために役に立つものではないと思います....)

. . . . . . . . . . . . . . . . . . . . .

これの制作は大きく分けて以下の3つのパートに分かれていると思っています。

  1. ツイートの収集・分析
  2. 学習・検証
  3. UI周り

どこの部分を取ってもとにかく失敗しかしていないのですが、一つずつ記録していきたいと思います。

あまりに長くなってしまいそうな予感がしているので、このブログでは「1. ツイートの収集・分析」にフォーカスして書こうと思います。

. . . . . . . . . . . . . . . . . . . . .

まず分析対象のツイートを集めなければならないのですが、ツイートを収集するにはTwitterAPIを使わないといけないと思います。私は当初大量のツイートを収集する予定だったのでAPIのレベルをElevatedに昇格させておきました。EssentialからElevatedにするには特別な申請が必要です。途中何度も同じようなことを書かなければならなかったりしますが、とにかく根気強く聞かれたことに正直に答えればそれほど問題なく昇格できた記憶があります。


ツイートの収集にはtweepyというライブラリを使用しましたが、もちろん使ったことがなかったので調べるのですが、私のAPIのバージョンがTwitter API v2なのですがほとんどの記事がTwitter API v1に関してだったので、そういうところも初めからある程度注意したほうがもっと効率的にできたかなと思っています。作ってる時にv2の記事はかなり少なかったですが、

tkstock.site

この記事を参考にして収集しました。このブログサイトにはかなり助けられた記憶があります。ありがとうございました。


以下がツイートを収集したときのコードです。(まあ↑のコピペみたいなものですが一応)

import tweepy 

#Twitter Deverloper Portalで取得したもの(Saveしろと言われたもの)を''の間に入れる
API_KEY = ''
API_KEY_SECRET = ''
BEARER_TOKEN = ''

#Twitter Deverloper Portalで生成したアクセストークンをコーテション('')の間にそれぞれ入れる
#ACCESS_TOKEN = ''
#ACCSESS_TOKEN_SECRET = ''

#リファレンスの内容に沿って入力(https://docs.tweepy.org/en/stable/client.html)
client = tweepy.Client(bearer_token = BEARER_TOKEN, consumer_key = API_KEY, consumer_secret = API_KEY_SECRET)


まずはとりあえずツイートを収集して、「バズ」「炎上」「その他」の基準を定義する必要があると考え、以下のようにして収集しました。

import pandas as pd
from datetime import datetime,timedelta

query = '。 lang:ja -is:retweet'       # 検索するキーワード 検索条件指定
limit = 20000              # 取得したいツイート数

# 取得対象のツイートの時間幅を指定する 
# この例では実行前の6日前から3日前の時間幅。
# UTC時間で指定しないと正しく時間指定ができないらしいです('+09:00'のところ)。

now = datetime.now()
now = now.replace(minute=0, second=0, microsecond=0)
end_time = now - timedelta(days=3) 
end_time_tweepy = str(end_time.isoformat())+'+09:00'
start_time = now - timedelta(days=6) 
start_time_tweepy = str(start_time.isoformat())+'+09:00'



df_tweet1 = pd.DataFrame()
for tweet in tweepy.Paginator(client.search_recent_tweets, query=query, start_time=start_time_tweepy, end_time=end_time_tweepy,
                              tweet_fields=['id','created_at','text','author_id','lang','public_metrics'], 
                              # user_fields=['description','protected','name','username','public_metrics','profile_image_url'],
                              expansions='author_id',
                              max_results=100).flatten(limit=limit):
    df_tweet = pd.concat([df_tweet1, pd.DataFrame([tweet.data])], ignore_index=True)

print(df_tweet)


ツイートの検索条件(↑だと'query =...'の部分)は「。」にしました。本当は日本語という検索条件だけでやりたかったのですが、queryなしでツイート収集する方法が見つからなかった記憶があります。


limitの部分は大きくしすぎると、リクエストが多すぎるとエラーが出ます。20000は(ギリ?)大丈夫でした。


流石に直近のツイートだといいねとかが集まってなさそうと思い、6日前〜3日前のツイートを収集しましたが、それ以上に深い考えはないです。もう少し時間幅を短くするとか、もっと古い時間にするとか工夫すべきだった気がしてきました....


いざ収集の部分('tweepy.Paginator...'の部分)ですが、'public_metrics'という項目にいいねの数、リツイートの数、引用リツイートの数、リプライの数の情報が辞書になってありました。


ツイート主のFFの数とかも調べたかったのですが、やり方がわかりませんでした。↑でコメントアウトしたuser_fieldは指定したところでどこに結果が反映されるのかわからなかったです。とりあえずauthor_idが取得できているので、再検索してFFの数の情報を得ようと思いましたが、for文で回した記憶がありますが、一向に結果が返ってこなかった記憶があります。FFが多い人の方がたくさんいいね・リツイートとかがつきやすいのは当然なので、ここの情報を得られないのはすごく痛かったですが、仕方なく妥協することにしました。


最後、いいね数とかがDataFlameで探しやすくするために'public_metrics'の中身をバラしました。(なんかすごいベタなやり方だけど...)

np_retweet_count=np.ones(limit)
for i in range(limit):
    np_retweet_count[i]=df_tweet["public_metrics"][i]['retweet_count']

np_like_count=np.ones(limit)
for i in range(limit*5):
    np_like_count[i]=df_tweet["public_metrics"][i]['like_count']

np_reply_count=np.ones(limit)
for i in range(limit*5):
    np_reply_count[i]=df_tweet["public_metrics"][i]['reply_count']

np_quote_count=np.ones(limit)
for i in range(limit*5):
    np_quote_count[i]=df_tweet["public_metrics"][i]['quote_count']

df_tweet["retweet_count"]=np_retweet_count
df_tweet["like_count"]=np_like_count
df_tweet["reply_count"]=np_reply_count
df_tweet["quote_count"]=np_quote_count


この後matplotlibで可視化してどのように「バズ」「炎上」「その他」を定義するかを決めようとしたのですが、全然うまく決められなかったし結局失敗したままです。ここが「1. ツイートの収集・分析」の一番の難所だと思います。ここを可視化してコネコネしてた部分のスクリプトがなぜか見つからないのですごく残念ですが....


まず「バズ」の定義に関してですが、そんなの簡単でいいねが1万以上なんじゃないの?と思われるかもしれないですが、実際収集してみると分かるんですが、そのようなツイートはあまりにも数が少ないです。その状態で後々学習させると、ほとんどがその他に分類されることになり(まあ「炎上」の定義方法にもよるんですが)、正解率がほとんどの場合で100%になるので学習が進みません。。(本当にバズるのってすごいんだなあという感情になりました。)


「炎上」についてはそもそもの定義が謎すぎるのでどうしたら良いのかという感じです.... 同学科に万バズも炎上も経験した人がいてその人に聞いてみると、炎上はいいねの数に比べて引用リツイートの数の割合がバズと全然違うよと言っていたので主に引用リツイートの数で判断することにしました。ただ、個人的には炎上してるツイートは結構リプで叩かれている感じもしていたので、結局(引用リツイート数)×0.7+(リプライ数)×0.2+(リツイート数)×0.1みたいな新しい変数を作ってそれを基準にすることにしました。どうするべきか全然わからなかったのですが、これ以上悩んでもキリがないと思い、えいやと短絡的に決めてしまいました。リツイートキャンペーンがそれなりにある印象だったのでリツイートの割合は小さくしました。


その後も難しい部分は続きました。なぜならほとんどのツイートがいいねも引リツもリツイートもリプも0で、得られた情報で「バズ」「炎上」「その他」の線引きをするのがあまりにも難しかったからです。ほとんどが「その他」になってしまい全く学習が進まないという状況に陥ってしまいました。(その時の図↓)

ファインチューニング失敗の図 学習が全然進んでいない....

結局3者がそれなりに妥当な数になるように、「いいね10未満のデータは全て切り捨てる」「バズはいいね50以上」「炎上は、(引用リツイート数)×0.7+(リプライ数)×0.2+(リツイート数)×0.1がいいねの1/5以上」と無理やり決めました。元々は100万ツイートくらい集めたのですが、上で示したコードを何回も実行してるので重複して収集しているものが多くて使えるデータは50万ツイートくらいでした。(ツイートのIDの重複があるものを削除しました。)そこから結局は1万3000ツイートくらいまで減りました。(具体的なデータはTwitterAPI申請時の取り決めにより示すことはできません....)

. . . . . . . . . . . . . . . . . . . . .

「1. ツイートの収集・分析」の部分での成長ポイントは、TwitterAPI・tweepyを使ってみたこと、実データを扱うことの難しさを痛感した、学習データがあまりにも偏っていると学習が全然進まないことを身をもって体験できたこと、だったかなと思います。改善ポイントは、データを恣意的な扱いをしてしまっていること、丁寧な分類ができなかったか、外れ値(リツイートキャンペーンとか)の排除方法についてもう少し考えるべきだったこと、かなという感じで、あとツイート主のFFのデータを集めると嬉しいなと思いました。


この記事はこれくらいにしようかな。「2. 学習・検証」「3. UI周り」に関しては別記事にしようと思います。


ではでは〜👋👋

初めて論文を読んでみてコードを動かしてみて自分のデータでやってみてのお話

こんにちは、takeshiho0531です。

 

実は12月からインターンを始めていて、たくさん勉強させてもらっています。

今回はそこでfew-shot segmentationというものについて勉強した(している)ので、それについて書きたいと思います。

---------------------------------------

few-shot segmentationとは、通常の深層学習を用いた画像認識タスクでは学習に大量のデータが必要になるのですが、そこを数枚の学習データととそのアノテーションデータを用意するだけで、特定のクラスのセグメンテーションができるようになるという技術です。(ざっくりしすぎ??)

 

特にその中でもBAMという2022年3月にChunbo Lang氏らによって発表されたモデルについて勉強しました。このモデルを選んだ理由は、「Few-Shot Semantic Segmentation」のPapers WIth Codeを見ていた時にCodeについている星が多くて超初心者の私でもなんとかなるかもしれないと思ったからです。

BAMのPapers With Code → 

Learning What Not to Segment: A New Perspective on Few-Shot Segmentation | Papers With Code

github → 

GitHub - chunbolang/BAM: Official PyTorch Implementation of Learning What Not to Segment: A New Perspective on Few-Shot Segmentation (CVPR 2022 Oral).

論文 →

https://arxiv.org/pdf/2203.07615v2.pdf

 

この論文で初めて、Computer Visionの分野の論文を読んでコードを動かしてみて自分のデータでもやってみるということをしました。コードを動かすというのはこの論文のgithubのUsageのStage〇と書かれた部分をやってみた、学習・検証を言われた通りにやってみたということです。学習・検証にはインターン先のGPUを使わさせてもらいました。

 

やったこととかに関しては別の記事にしてちゃんと書きたいなと思っています。この記事ではやってみて(全然終わったわけではないが一区切りはついたかなという感じのタイミングなので)感じたこととかを忘れないうちに残しておこうと思います。

 

やったことなんて、特に学習・検証の部分はもうすでに書かれたコードを言われた通りに動かしたことだけで、難しいことは何一つとしてしていないのですが、長く苦戦していました。このパートでうまくいかなかった原因は、適切にファイルを置くことができていなかったからだと思います。もっとも多かったのはダウンロードがうまくできておおらず歯抜けになっていたり壊れていたりしていたこと(なんにせよデータがものすごく重たいので...)、あとは元々書かれたパスを適宜書き直さないといけなかったことが多かったという印象があります。こんなところで失敗するなんて考えていなかったので経験を積んでみるというのは大事なんだなと実感しました。自分のデータでやってみる、というパートで苦戦した理由は、自分の勉強不足だったと思います。具体的には転移学習って何、ファインチューニングって何、重症なものだと推論がよく分かってなかったというのが挙げられます。推論がわかっていなかったというのは重症で、私の場合は単に

可視化するのと何が違うのか、model.eval()にしたらどうなるのか、evalモードになったmodelになんの引数を渡せば推論ができるのかといった基本的なことがよくわかっていませんでした。torch.nn.Moduleの挙動が全くわかってなかったです。改めて自分ってカスなんだな〜となってかなり落ち込みましたが、でも実際に自分でやってみるのは本に書かれたことをなぞっただけの知識を自分のものにしていくのにすごく大事なんだなあとなりました。ちょっとはわかるようになったかな?

 

---------------------------------------

さて、すごく眠くなってきたのと1.5日後くらいに締切のレポをやらなければいけないので今日はこれくらいでご勘弁ください。

ではでは〜👋👋

 

2022年 振り返り

こんにちは、takeshiho0531です。

 

まめじゃないせいでお久しぶりになりました。黒豆は前の正月もいっぱい食べたはずなのになあ。

今日は大晦日ということで、2022年の振り返りをしようかと思います。ただ、年末という実感が全く湧かずにブログを書くのを引き延ばしすぎた結果、あと30分くらいで2022年が終わりそうという状況で、だからざっとだけ振り返ります。

 

---------------------------------------

 

2022年は正直なところ人生が大きく変わったと言っても良いほど大きな変化を遂げた一年でした。今までの人生の一年の中では一番人生が変わったと思います。あまりにも大きく変わってしまったので、自分が別の人間の人生を途中から乗っ取ってしまったのではないかと思うこともあるくらいです。だからこそ、30分しかなくてもブログに残しておきたいと思うわけです。

 

1月・2月は、それまでの私から大きな変化はなかったと思います。元来向上心が強い人間なので今のままではダメ、強みを持ってもっと輝く人間になりたいという強い思いが疼いてはいましたが、蓄積はされていたものの実際の変化に繋がることはなかったと思います。ただこの時期の自分を褒めてあげたいのは試験・レポートを頑張ったこと。1Sの成績が凄惨を極めていて進振りに関しても結構絶望的な時期でテスト勉強中も何度も未来のことを考えて心が折れかけたのですが、目の前の課題に精を出すことに集中できたのは本当によかったです。おかげで成績はなんとか持ち直し、進振りにも少しの希望が持てるようになりました。

 

春休みは普通に遊んでいました。大切な人と大切な思い出が作れたという観点では貴重でした。特に勉強はしてなかった気がしますが、それでもいっぱい遊んで充実していたという実感があるので許します。

 

2Sセメスターになり、進振り先を真剣に考えるようになりました。1Aセメスターの成績がそれなりに良かったので、情報系の学科にも進学できるかも?という状況になりました。昔からパソコンカタカタしてる人はカッコいいって思っていたこと、数学物理と違って明らかな不適性であるということが確定はしていなかったこと、食べていくのにも困らなさそうなこと、こういった理由で情報系に行きたいかも?と思い始めていました。(だからといって、アルゴリズム入門の授業もも真面目に取り組んでいなかったし自分からプログラミングの勉強をすることもなかったわけだが...) 進振り先は本当に真剣に考えていて、自分の基本平均点、どこの大学院に行けるのか、周囲のレベル、自分の性格との相性、授業等を総合的に考え、友人にも何度も相談に乗ってもらいました。感謝しています。何度迷っても結局ここに戻ってくるということで、工学部システム創成学科Cコースに進学し大学院は海外の大学院に進学するということを心に決めました。この選択は現時点では最適解だったのではないかと改めて思います。

 

進学先が固まり始めた私は、何か強みが自分に欲しいと思い始めました。私は劣等感を異常に感じてしまう性格でそれゆえに精神状況が悪化することが多い人間だったからです。潰されないための強みを渇望するようになりました。そこで新しい学科の人と出会うまで数ヶ月は猶予があるから、その間でも何かを頑張ろうと思いました。そこで選んだのは機械学習でした。特に深い理由もなく、大学に入って出会った先輩がKaggleとか言ってたのを思い出してなんかカッコ良さそう??と思ったくらいの理由です。ただこの分野を選んだのはそれなりに良かったのではないかと思います。今すごくホットな割に2年になったばかりの人で機械学習を本格的に勉強している人は、機械学習のホットさから考えればかなり少ないからです。(個人的な感覚ですが。) やっぱり他人ができないことができると、私ってもしかしてできるかも?みたいな勘違いからもっと勉強したい!!差をつけたい!!と(少なくとも私は)なります。先取りして勉強してみたおかげで自己肯定感が異常に低い私も少し自信を持つことができるようになり、挑戦する勇気のようなものを得ることができました。海外の大学院という選択肢を持つことができたのも自信が持てたおかげだと思います。それまでは、遥かに届かないことを自覚するのが怖くて留学等を意識高い系のやることだろと冷笑して目を背けていた立場だったのですが、このままでは届かないという事実を正面から受け入れられることができました。また、灯というAIを勉強するサークルに加入することができたこと、機械学習コンペのグループに参加することができたことなども自信が少し持てたおかげで足を踏み出せた結果だと思います。

 

また、機械学習の勉強を始めて良かったと思うことは深層学習というものを知れたことです。『ゼロから作るDeep Learning』のシリーズに出会えたことは幸せでした。(まだ3巻目の途中ですが...) 一から作ることでなんだか愛着みたいなのが湧きました。深層学習は今まで聞いたことある?くらいの存在でしたが、気づけば有用さ不思議さに興味を持つようになっていて自分でも驚きました。

 

あとこれも言っておきたいということですが、今年素敵な言葉と出会いました。

 

 

そう、運は自分で引き込まないといけないんだ!特に私のような凡人の中の凡人にチャンスが空から降ってくるなんて幻想なんだ、全力でチャンスを引き込まないといけないんだ!と思いました。正直この言葉と出会ってから心の持ちようが変わったとはっきり感じます。だから、2023年の春休みの短期留学に申し込んだりや夏休みの海外の研究インターンに応募したりインターンのお声かけにすぐに乗ったりしました。次はこの姿勢を維持しつつ掴みに行ったチャンスを120%活用できるように努力したいですね。

 

これで最後くらいにしようかと思うのですが、海外院進を視野に入れるようになってから、ロールモデルのような方を見つけることができました。その方が自分の夢を叶えた軌跡や気持ちの持ち方を積極的に発信してくださるおかげで、孤独に耐えることができたり重大な決断を下すことができたりした瞬間が数えきれないくらいあります。本当に憧れる方を見つけることができて、人生がますますチャレンジングなものになっている実感があり、本当に感謝しています。

 

留学のことを考えてる時に頭に浮かぶ歌詞

「ただひとつの夢 決して譲れない 心に帆を揚げて 願いのまま進め いつだって あなたへ 届くように 歌うわ 大海原を駆ける 新しい風になれ」

「悲しみも強さに変わるなら 荒れ狂う嵐も越えていけるはず」

「目覚めたまま見る夢 決して醒めはしない 水平線の彼方 その影に手を振るよ いつまでも あなたへ 届くように 歌うわ 大きく広げた帆が 纏う 青い風になれ」

(『風のゆくえ』より)

 

 

---------------------------------------

 

なんだかダラダラとまとまりなく書いてしまいましたが、まあいいや。これ生意気なことばかり書いているから将来見返したらすごい恥ずかしいんだろうな.....

もう日が変わってしまいましたが、私が寝るまで日は変わらないという認識なので!!(←???????)

 

ではでは〜👋👋

『PyTorchによる発展ディープラーニング』1-4 AWSのAMIの選び方

こんにちは、takeshiho0531です。

 

先週が東大ではA1タームという学期(??)の末で、それのちょっと前からレポートなりテストなりに追われていて久しぶりのブログ投稿になってしまいました。。

 

以前のブログでも『PyTorchによる発展ディープラーニング』に関して触れていましたが、一応一読はしました!今回はその中で初っ端から詰まってしまったAWSインスタンスの選び方について、私はどうしたのかを書いてみることにしました。

---------------------------------------

 

『PyTorchによる発展ディープラーニング』の中では1-4で選ぶインスタンス、起動のさせ方など詳しく書かれていますが、書かれた時から時間が経っているせいか、本の中で言及されているAMI(Amazon Machine Images)を見つけることができませんでした。

 

どうしたのかという結論から言うと、クイックスタートAMIから「Deep Learning AMI GPU PyTorch 1.12.1」というのを選びました。

AWSの画面のスクリーンショット

クイックスタートに入ってるくらいだからよく使われているということじゃないかなと思ったからです。このAMIに関しての説明は、

aws.amazon.com

に載っていましたが、

"Initial release of Deep Learning AMI GPU PyTorch 1.12 (Ubuntu 20.04) series. Including a conda environment pytorch for python3.9 complimented with NVIDIA Driver R510, cuda=11.6.2, cudnn=8.4.1, nccl=2.12.12, and efa=1.16.0."

とあり、conda環境でPyTorchが使えることを確認し、これにしました。

 

これに関しては他にも注意点があって、本の中ではp2.xlargeのインスタンスを起動させるように書かれていますが、スクショのところに書かれている通り、"Non-supported GPU instances: P2"と書かれています。上にリンクを貼った、AMIの説明に関するページで詳しくサポートされているインスタンスが記載されていますが、私はその中でもP3インスタンスのp3.xlargeを選びました。(私が使っていた時はP2インスタンスがサポートされてないってはっきり書かれていなかった気がしていて、実際コードを書いて

torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

としても"cpu"が返ってきてしまって??となっていたところ、

Why is the GPU not working out of the box for Deep learning AMI EC2 instance? | AWS re:Post

のページを見つけて原因がわかり、解決できたという感じでした...)

 

さらになんですが、p3.xlargeにした場合、最低でも8vCPU必要です。ですが、現在(2022.12)はどこのリージョンでも制限がとても厳しく、大体どこのリージョンでも0vCPUが上限となっており1つもインスタンスが起動できない状況だと思われます。(制限は左のリストの「制限」というところから「制限の検索」という検索窓に「インスタンス」と入れるとすぐにみれました。)

AWSのEC2の画面

なので、この制限を緩和してもらう必要があります。まずはAWS Support Centerにアクセスして、"Open Support cases"というところからCreate caseをします。

AWS Support Conterの画面

Create caseボタンを押すと画面が遷移し、"Looking for service limit increases?"をクリックします。

Looking for service limit increases?

その後は、Limit typeをEC2インスタンス、プライマリインスタンスタイプをAll P instances、New limit valueを8にして欄を埋めていって一番下のSubmitボタンを押せば大丈夫だと思います。

結論から言うと、多分このリクエストは却下されます。ですが、そこでめげずにReopen caseをして、勉強に必要でこの勉強をしないと〇〇でダメなんです😢、みたいな感じで正直に書いて、個別に対応してもらうと上限を緩和してもらえると思います。担当者の方には丁寧に対応していただき、感謝しています。

 

最後に注意点ですが、P3インスタンスはP2インスタンスよりも値段が高い(私がやっていた時はP2の3倍くらいの値段が毎時発生していた)ので、安いリージョンでインスタンスを起動することと必要ない時はインスタンスを停止し忘れないようにすることが大事だと思いますー

 

---------------------------------------

なんだかすごく長くなってしまいましたが、ただの初心者のブログですが、少しでもお役に立てれば幸いです(思い上がりか?)。読んでくださった方、本当にありがとうございます。

こういう系のブログばっかになるのもなんだか嫌だなあ。考えます。

ではでは〜👋👋

『PyTorchによる発展ディープラーニング』2-2 VOCのデータをダウンロードできない場合

こんにちは、takeshiho0531です。

訳あって『PyTorchによる発展ディープラーニング』を短期間で読了しなければいけない状況に追い込まれています(現在進行形)。早速始めたはいいものの、初っ端から非本質的な部分で詰まるところがあって時間がもったいなかったなと思った部分があったので、そこに関してどう解決したかを書いてみることにしました。

 

第1章のAWSインスタンスの部分もブログを書こうと思ってたのですが、今また問題が再燃してるのでそれが解決してから書きます。今回は第2章で使うVOCのデータセットが、VOCのサイトにアクセスできなくてダウンロードできないじゃん!ってなった時にどう解決したかを書きます。(少なくとも私が取り組んだ時期にはアクセスがずっとできませんでした。)

 

解決策は簡単で、別のサイトでVOCのデータをダウンロードできるようにしてくれている大変親切でありがたいサイトを発見したので、そこからダウンロードしました。

pjreddie.com

 

「make_folders_and_data_downloads.ipynb」のダウンロード先のURLをこのサイトのVOC2012のTrain/Validation DataのURL(http://pjreddie.com/media/files/VOCtrainval_11-May-2012.tar)に貼り替えておけば大丈夫だと思います。

 

めちゃくちゃ親切でありがたかったので、サイト運営をされているJoseph RedmonさんのTwitterフォローしときましたw

 

ではでは〜👋👋

ブログを開設しました!

初めまして、takeshiho0531です。

ブログ開設の挨拶という感じで1回目のブログを投稿します。 (そうでもしないといつまで経っても1投稿目をしなさそうなので。)

今まで何度もブログを開設してはしばらく経って捨てて、ということをn回繰り返してきたので、このブログがいつまで存在するのかも極めて怪しいですが、気が向いたら投稿するようにしようと思います。

ジャンルとかは決めずに気が向いた対象を投稿していくつもりです。もしちゃんとブログが溜まってきたら、その時に考えます。

 

他に書くこともないので軽くだけ自己紹介しておきます。

現在東京大学理科一類の2年生で、3年生にちゃんと進級できたら工学部システム創成学科Cコースというところに進学します。何をする学科なのかを聞かれると困っちゃうので聞かないでください。何に興味があるのかっていうのも自分でもよく分からないような大学生ですが、このブログが溜まってきたら何らかの興味の方向性とかも自分で掴めるようになるのかな。

なんかこれ以上書くこともない気がしてきた、もういいや。

 

ではでは、よろしくお願いします。