かんちゃんの備忘録

プログラミングや言語処理、ガジェットなど個人の備忘録です。(メモ書き+α)

サポーターズ勉強会でPythonでのスクレイピングについて登壇しました

10月10日に「Pythonで始めるスクレイピング」というタイトルでサポーターズColabで登壇しました。

発表内容の概要と振り返りを書こうと思います。

発表内容

初心者を対象に、Python言語によるWebクローリングとスクレイピングについて説明とハンズオンを行いました。

前半はスライドを用いた説明です。

導入は、そもそものクローリングとスクレイピングの概念について混同しがちな部分であるため、図を用いて整理しています。 実際にPythonライブラリを用いてページのフェッチとスクレイピングを行う方法として、requestsモジュールやBeautifulsoupなどメジャーなライブラリを紹介しました。

中盤は、Scrapyの概要やヘッドレスブラウザでの描画について述べました。 また、実際にクロールする際のマナーについてもアクセス数や頻度に注意することや、連絡先を明記することなど気をつけることがたくさんあります。

後半はGoogle Colabratoryを使ったハンズオンです。

前半のスライドの内容を実際に動作させることができます。

振り返り

良かった点は、「こんな方にオススメ」という人にしっかり伝わる資料ができたことだと思います。 スクレイピングを始めたいけど一歩目が難しい人の後押しにしっかりなったかと思います。

いまいちだった点は、時間配分です。 後半のハンズオンが非常に駆け足になってしまいました。 スマホで時間を確認すればよかったのですが、PCはスライドショーモードにはなっておらず時計が見えませんでした。 また、会場の時計も見つけることができず時間配分が感覚になってしまいました。 よほど練習した資料であれば何となくでも時間はわかるものですが、今回はそこまで詰め切れていませんでした。

ただ、コメントをかなり丁寧に書いたつもりですので、持ち帰ってじっくり見ていただけるかと思います。

ご参加いただいたみなさまありがとうございました

スクレイピングを始めてみたい人や、機械学習の学習データとしてあれこれしたい人にとってぴったりの勉強会だったかと思います。

会場の準備や勉強会登壇の機会を与えてくださいましたサポーターズCoLabさんには感謝しております。

最後に、ご参加いただいたみなさまありがとうございました。

Gitレポジトリ内でのNameやEmailの設定

一つのGitアカウントで、会社と個人の両方を使っています。 そうした場合に、会社のレポジトリでは会社のEmail、個人のレポジトリでは個人のEmailをコミットログに残しておきたいです。

そのための設定の備忘録です。

localで設定

単純です。 作業対象のGitレポジトリだけに有効なローカルの設定を適用します。

$ cd git_repository
$ git config --local user.name "First Last"
$ git config --local user.email name@domain

上記の設定により、レポジトリルート以下の .git/config ファイルに次の内容が記述されます。

[user]
        email = name@domain
        name = First Last

どうしてlocalで指定するのか

あるディレクトリ以下のレポジトリに対しては、ある設定ファイルを適用するという方法があります。 この場合、設定したことを忘れて影響範囲がよくわからなくなりそうです。

globalに設定するとlocalで指定することを忘れてしまいます。

この2つのパターンから、globalには設定せずに、localで指定するのが良さそうと考えました。

local適用パターンでちょっと運用してみようと思います。

はてなブログ記事中の外部ドメインへのリンクを新規タブで開くようにスクリプトを仕込む

はてなブログの記事をMarkdownで書いているのに、リンクを新規タブで開くようにしようと思うと、素のHTMLを書くことになります。

せっかくのMarkdownのうまみが減ってきて、重量級マークアップになってしまいます。

今回はドメインが異なる場合には、新しいタブに飛ばしたいという要望がありました。 それに対して、自動で新規タブを開くように書き換える処理を素のJavaScriptで書きました。

下調べ

jquerylocation.hostname が異なることを検知して、属性を書き換えているかたが多いようです。

試してみたところ、jqueryを読み込む処理を記述しないと動作しませんでした。 はてなブログ自体もjqueryが使われているため、むやみやたらにimportは少々懸念材料でした。

そこで素のJavaScriptで書いてみました。

JavaScriptで書いてみる

jqueryでやっていることをできる限り移植しました。 ヘッダーかフッターに挿入することで動作します。

<script type="text/javascript">
const a_tags = document.getElementsByTagName("a")

for(let a_tag of a_tags) {
    if (a_tag.href.startsWith("http") && !a_tag.href.match(location.hostname)) {
        a_tag.setAttribute("target", "_blank");
    }
}
</script>

ページ内のaタグのうち、httpで始まっており location.hostname が含まれない場合に、新規タブで開くようにしています。

match を使っているので正確ではない場合はあるかもしれませんが、おそらく滅多にないでしょう。

これで意識せずに、自ドメイン以外のサイトのリンクは、HTML読み込み後に自動で書き換えてくれます。

JavaScriptは詳しくないので、もっとよい書き方があればぜひ教えてください。

会社の勉強会で話しました

7月18日に自社開催の勉強会で登壇しました。

記事を書こうと思っていたのですが、気がつくと1ヶ月ほど経っていました。。。 (すぐに書こうと思っていたはずなのに、ボーッとしていた)

勉強会で話したことや感想を書きます。

自然言語処理(NLP)領域に関わっていると避けては通れないあの話」

この壮大なテーマのもと、第1回目となるR&Dの外部向け勉強会を開催しました。 多大なサポートをいただいた人事部のみなさまには本当に感謝しています。

本勉強会で何を話そうか正直結構悩んでいました。 本発表自体も特別すごいアルゴリズムや高度な手法を使ったわけでは無く、割と地味なことに取り組んでいます。 しかしながら、実際にサービスと稼働していることから、実際の開発と運用における話ができると思い、このテーマに決めました。

Eightニュースフィード活性化のための自然言語処理の取り組み

第1回目の1番目に少々緊張しながら「Eightニュースフィード活性化のための自然言語処理の取り組み」について発表しました。

具体的には、ニュース文中に出現する企業のタグ付けアルゴリズムの開発と、アルゴリズムAPI化について紹介しました。

もしかすると、理論を勉強することが目的だった人は、物足りなかったかもしれません。

割と仕組みの概略はわかりやすいのですが、実際に実装すると躓く点を中心に説明しています。 例えば、Webページから本文を抽出したり、企業を特定したりすることです。

アルゴリズムを成立させるために、本文抽出アルゴリズムpython-extractcontentPython3対応させました。

企業辞書と企業特定は、母体となるアプリケーションの都合上、必要不可欠な項目です。 企業特定とは、企業名だけですと同名企業があるため、どの企業かの曖昧さを解消するというものです。

曖昧さ解消は現状困難であるため、今回は候補出しというタスクに変えました.

この曖昧さは、企業辞書がリッチだからこそ起こる問題です。 たとえば業界情報を付与すれば、業界と共起しやすい単語で企業を特定できるのではないか?などいくつかアイデアは浮かびます。 実際取り組もうと思うと、そもそものリソース構築の難しさにやられてしまいます。

そういった難しさや対処について紹介しました。

ご参加いただいたみなさま、どうもありがとうございました。

サポーターズ勉強会で文書分類についてハンズオンを行いました

7月31日に「文書分類で自然言語処理に触れる」というタイトルでの講師を行いました。

このような機会をいただき、どうもありがとうございました。

本記事では、題目の理由や講演の振り返りを行います。

続きを読む

系列ラベリングの素性抽出

系列ラベリング問題を取り扱う際の素性抽出が、いつも複雑になりがちなので、テンプレートを書いてサクッと抽出できるよう整理しました。

どんな素性を抽出したいか

固有表現抽出を例にあげます。

以下の表は、「午前8時に東京駅で集合する。」という文を形態素解析し、IOB2(Inside-outside-beggining)タグ形式で固有表現のラベルを付与したものです。

単語 品詞 IOB2タグ
午前 名詞 B-TIME
名詞 I-TIME
名詞 I-TIME
助詞 O
東京 名詞 B-LOCATION
名詞 I-LOCATION
助詞 O
集合 名詞 O
する 動詞 O
記号 O

「午前8時」はTIME属性、「東京駅」はLOCATION属性を持つことになります。

ここで、「東京」という単語を例に素性抽出します。

素性には、対象単語と前後2単語の表層形、対象単語と前後2単語の品詞、推定済みの前2単語のIOB2タグを利用するとします。 あるラベルを学習する際の素性は、Pythonの辞書形式で表すと以下のようになります。

{
    "word-2": "時",
    "word-1": "に",
    "word": "東京",
    "word+1": "駅",
    "word+2": "で",
    "pos-2": "名詞",
    "pos-1": "助詞",
    "pos": "名詞",
    "pos+1": "名詞",
    "pos+2": "助詞",
    "iob-2": "I-TIME",
    "iob-1": "O"
}

これを簡単に抽出できるように、テンプレート作ります。

テンプレートを使って素性抽出

テンプレートは、「ラベル名、素性抽出のための関数、対象単語からの相対的な位置」を持ちます。 素性抽出のための関数は、対象の素性を抽出するための関数で、例えば小文字かどうかを素性に含めたい場合は lambda x: x.surface.islower() のような処理を記述します。 例では、あるトークンのインスタンス変数としてsurfaceを持っているため、x.surfaceでアクセスしていますが、x['surface']のように与えるトークンの形式により異なります。

# 素性抽出のための関数
word_feature = lambda x: x.surface
pos_feature = lambda x: x.pos
iob2_feature = lambda x: x.iob2

# テンプレート
templates = [
    ("word-2", word_feature, -2), ("word-1", word_feature, -1), ("word", word_feature, 0), ("word+1", word_feature, 1), ("word+2", word_feature, 2),
    ("pos-2", pos_feature, -2), ("pos-1", pos_feature, -1),("pos", pos_feature, 0), ("pos+1", pos_feature, 1), ("pos+2", pos_feature, 2),
    ("iob2-2", iob2_feature, -2),  ("iob2-1", iob2_feature, -1),
]

テンプレートを適用し素性抽出を行うメソッドを書きます。

def iter_feature(tokens, templates):
    tokens_len = len(tokens)
    for i in range(tokens_len):
        # バイアス項
        feature = {"bias": 1.0}

        # テンプレートを適用
        for label, f, target in templates:
            current = i + target
            if current < 0 or current >= tokens_len:
                continue
            feature[label] = f(tokens[current])

        # BOSとEOS
        if i == 0:
            feature["BOS"] = True
        elif i == tokens_len - 1:
            feature["EOS"] = True

        yield feature

たとえば、scikit-learnを利用する場合は、辞書形式で抽出された素性を、DictVectorizerでベクトル化することで、利用可能となります。

features = []
for tokens in corpus:
    features.extend(iter_feature(tokens, templates))

# from sklearn.feature_extraction import DictVectorizer
feature_vectorizer = DictVectorizer()
vec = feature_vectorizer.fit_transform(features)

同じテンプレートで推定を行う

実際の推定時には、以下のように先頭から順に推定し、推定したタグを代入していくことで、次のタグの素性抽出時に推定した前のタグが利用可能になります。

# tokensは推定したいトークン列, templatesは学習時と同じものを利用
for token, feature in zip(tokens, iter_feature(tokens, templates)):
    # 素性抽出
    vec = feature_vectorizer.transform(feature)
    # tokenのiob2変数に推定値をセットする
    token.iob2 = label_encoder.inverse_transform(model.predict(vec))[0]

Google Compute EngineでPythonのGPU環境を構築

機械学習環境として、Google Compute Engine(GCE)のGPUインスタンス上にPythonを構築しました。 また、GPU対応版のLightGBMとCatboostをインストールしました。

本記事はその備忘録となります。

続きを読む