推しの方々からいいねをもらったりもらわなかったり, 評判がなかなか良いです(?)
全体のソースコードと詳細は https://github.com/camberbridge/dft-gcp-serverless にありますのでよろしければご覧ください.
何と言っても特筆すべきは Cloud Functions の柔軟さです.
関数が何らかの原因でクラッシュしても, 「失敗時に再試行」の設定をしておけば最大7日間 (2021年現在), 関数が正常に完了するまで繰り返し呼び出されます (タイムアウト時間は設定可能).
また, リクエストの多さによってインスタンス数が自動でスケールします. もちろんこの時の上限数は設定可能です.
今回は, 毎分の小さな定期実行なのでスケーリングしなくても間に合うだろうと思っていましたが, 画像付きツイートが多いと (顔認識があるので) 関数の処理に時間がかかって次のリクエスト処理に間に合わないことが多々ありました.
そんなときに Cloud Functions は自動でスケールアウトして, 逆に画像付きツイートが少ないとスケールインしてくれて, 非常に便利でした.
Twitterのホームタイムラインを定期的に取得する際, 前回取得したTweet IDよりも新しいものだけを取得するようにしたかったので, 最新のTweet IDを保持しておく必要がありました.
そこで, (DBを用意するまでもない構成ですが) 環境をステートレスにしたかったこともあり, ドキュメントDBの Cloud Datastore を用いました$^1$.
Tweet IDを書き込むときはデータを増やす理由がないので同じエンティティを更新し続けるようにしました.
ベストプラクティス的には, 「1つのエンティティをあまり頻繁に (1秒に1回以上) 更新しないこと」とありますが, ここでは毎分の更新なので良しとします$^2$.
1 | from google.cloud import datastore |
※ローカル環境から Datastore にアクセスする (上のコードを実行する) 場合はサービスアカウントを発行する必要があります$^3$
イベント単位の実行に適したサーバーレスコンピューティングリソースの Cloud Functions を用いました.
基本的にはGCPコンソールで作成と設定が可能ですが, 顔認識に必要なOpenCVのDNNモデル等のファイルをコンソールからアップロードできません.
そこで, ソースコードやアセットをzip化したファイルを gsutil で Cloud Storage にアップロードしておき, その後の gcloud による Functions デプロイ時に, オブジェクトURLを指定してあげます$^4$.
1 | zip -r [FILE_NAME].zip * |
cronと同じ要領で使える Cloud Scheduler から毎分 Pub/Sub トピックをパブリッシュして Cloud Functions をトリガーさせました.
トピックは gcloud コマンドでデプロイする時に作成 (Pub/Sub_TOPIC_NAME の部分) しており, 受けの Functions にも設定しているので, あとは出す側の Cloud Scheduler の設定をします.
やり方は, GCPコンソールでリージョンやcron, トピックを指定するだけです$^5$.
デプロイ時やプログラム実行時の成功/失敗など, ログ監視に Cloud Logging が大変役に立ちます.
また, エラー確認には, Cloud Error Reporting がすごく便利です. Twitter APIにレート制限があることをすっかり忘れていて, 実際に Error Reporting からメールが届いたおかげで気づけました (後述の wait_on_rate_limit を設定したきっかけ).
無料アカウントの場合, ホームタイムライン取得リクエストの制限回数は15分に15回 (2021年現在) です$^6$.
結構シビアですが, Tweepyにはレート制限が補充されるのを自動的に待つ (エラーハンドリングせずに放置できる!) 便利なパラメータ wait_on_rate_limit があるのでそれを使います$^7$.
1 | import tweepy |
今回の仕組みは定期実行させるので, 毎度同じツイートを取得する可能性があります.
そこで, これまた便利なパラメータ since_id を使い, あるIDよりも新しいツイートを取得するようにします.$^7$
1 | timeline = api.home_timeline(since_id=TWEET_ID, count=N) |
推しが投稿するツイートだけを取得したいので, 前処理として”RT @”を含むツイートを除外します.
ちなみにただのリツイートはホームタイムライン取得のレスポンスには入ってきません.
1 | for status in timeline: |
元ツイートのURLをTweet本文に貼るだけで引用リツイートになります.
そしてアプリでリツイートした時と同様に, きちんと引用されたユーザに通知されます.
OpenCVでいろいろと画像処理をするため, Media URLから画像を一時ファイルとしてダウンロードしました.
このとき画像は /tmp ディレクトリに保存することがポイントです.
これは, Cloud Functions のファイルシステムで書き込み可能な場所 (一時ファイルの保存先として使用できる場所) は /tmp ディレクトリだけとなっているからです$^8$.
そして画像ファイルをOpenCVで読み込み, 処理の高速化のためあらかじめサイズを半分にしておきました.
1 | import numpy as np |
画像をOpenCV DNNに入力する前に, 画像を正方形にリサイズしておく必要があります.
OpenCV DNNの顔認識では最初にBlobオブジェクトを blobFromImage() で作るのですが, このときの処理が同じ幅と高さの画像を要するからです$^9$.
1 | img_size = 600 |
検出結果それぞれには信頼度があるので, それをよしなに使います.
いろいろと試行錯誤した結果, 0.9よりも大きければ顔と判定することにしました. 今のところ仕組みは顔写真付きツイートだけに反応できているのでいい感じです.
# 事前学習済み DNN caffe model
# 参考: https://github.com/spmallick/learnopencv/tree/master/FaceDetectionComparison/models
PROTOTXT_PATH = "./deploy.prototxt"
WEIGHTS_PATH = "./res10_300x300_ssd_iter_140000_fp16.caffemodel"
# 信頼度閾値
CONFIDENCE = 0.9
face_num = 0
# モデルのロード
net = cv2.dnn.readNetFromCaffe(PROTOTXT_PATH, WEIGHTS_PATH)
net.setInput(blob)
detections = net.forward()
for i in range(0, detections.shape[2]):
confidence = detections[0, 0, i, 2]
# 顔検出
if confidence > CONFIDENCE:
face_num += 1
[1] “Datastore mode Client Libraries”. https://cloud.google.com/datastore/docs/reference/libraries
[2] “ベストプラクティス - エンティティの更新”. https://cloud.google.com/datastore/docs/best-practices#updates_to_an_entity
[3] “Datastore mode Client Libraries - Setting up authenticaiton”. https://cloud.google.com/datastore/docs/reference/libraries#setting_up_authentication
[4] “Google Cloud Pub/Sub トリガー - 関数のデプロイ”. https://cloud.google.com/functions/docs/calling/pubsub#deploying_your_function
[5] “Pub/Sub を使用して Cloud ファンクションをトリガーする - Cloud Schedulerジョブを作成する”. https://cloud.google.com/scheduler/docs/tut-pub-sub#create_a_job
[6] “Get Tweet timelines - Resource Information”. https://developer.twitter.com/en/docs/twitter-api/v1/tweets/timelines/api-reference/get-statuses-home_timeline
[7] “Tweepy API Reference”. https://docs.tweepy.org/en/latest/api.html
[8] “Cloud Functions 実行環境 - ファイルシステム”. https://cloud.google.com/functions/docs/concepts/exec#file_system
[9] “OpenCV Deep Neural Network module”. https://docs.opencv.org/master/d6/d0f/group__dnn.html
]]>Jaccard係数は集合間の類似度を表す尺度(値域は0から1の間)であり, (1)式により定義されます(値が1に近づくほど類似度が高い).
$sim(C_i, C_j) = \frac{\mid C_i \cap C_j \mid}{\mid C_i \cup C_j \mid}$ ・・・(1)
一般に, (1)式はDBの中からクエリqとのJaccard係数が大きいデータ(集合)を探すような場合に, データ数nやデータの要素数dが大きいと計算時間($O(nd)$)が非常に大きくなる問題があります${}^{※1}$.
そこで今回は, 集合に対する確率的なハッシュ関数であるMin Hashを用いて, Jaccard係数を近似計算する実験を行ってみます. Min HashはJaccard係数に対するLocality Sensitive Hashingです.
※1 扱うデータはそれぞれ要素の種類数を次元とした2値ベクトル表現に変換しているとします.
2つのベクトル$C_i = (1, 1, 0, 0)$, $C_j = (1, 0, 1, 0)$があるとします.
タイプA | タイプB | タイプC | タイプD | |
---|---|---|---|---|
$C_{i}$ | 1 | 1 | 0 | 0 |
$C_{j}$ | 1 | 0 | 1 | 0 |
このとき各列は値によって4種類に分類でき, タイプDはJaccard係数に関係ないので, $C_i$, $C_j$間の類似度は(2)式で表現できます.
$sim(C_i, C_j) = \frac{\mid A \mid}{\mid A \mid + \mid B \mid + \mid C \mid}$ ・・・(2)
Min Hashは, ベクトルの列入れ替え規則を持ったハッシュ関数mhをそれぞれのベクトルに適用した後, 最初に非ゼロが出現する位置(ハッシュ値)が一致する確率によってJaccard係数を近似する手法です. したがって, ベクトルの列をランダムに入れ替えたときにタイプA, B, Cのどれが最初に出現するかでハッシュ値が一致するかどうかが決まるため, 結局タイプDは関係なく, (3)式で表現されます.
$P[mh(C_i) = mh(C_j)] = \frac{\mid A \mid}{\mid A \mid + \mid B \mid + \mid C \mid}$ ・・・(3)
なお, 実際にMin Hashを使うときはベクトルの列を入れ替えることはせずにランダムに生成したハッシュテーブル$r_{(t)}$を用意し, ハッシュテーブルを通してハッシュ値を求めます(データベースで列の入れ替えは処理が重いため).
例:$r_{(t)} = $ {$6, 1, 2, 3, 8, 7, 5, 4$}, $C_i = (1, 0, 0, 0, 1, 1, 1, 0)$のとき, $mh(C_i) = min${$6, 8, 7, 5$} $ = 5$
つまりMin Hashは作成したハッシュ関数(ハッシュテーブル$r_{(t)}$)をそれぞれの集合に適用した後に最小値を求め, それが一致する確率でJaccard係数を近似します. ハッシュ値が一致する確率は経験確率により近似計算します. 具体的には,
kの数を増やせば増やすほど, 近似値はJaccard係数に近づきます.
※2 k個のハッシュ値はsketchとも呼ばれます.
ここで重要な定理は, $C_i$, $C_j$のハッシュ値が一致する確率はJaccard係数と等しいということです.
$P[mh(C_i) = mh(C_j)] = \frac{\mid C_i \cap C_j \mid}{\mid C_i \cup C_j \mid} = sim(C_i, C_j)$ ・・・(4)
Jaccard係数とMin Hashを実装します. すべての集合を2値ベクトル表現にし, プログラムの実行時に引数でsketchの数kを指定できるようにしました.
1 | # -*- coding: utf-8 -*- |
それではハッシュ値の数k = 5で実行してみます. なお, まとめたコードはGitHubにあげています.
1 | $ python jaccard_minhash.py 5 |
次はk = 7で実行.
1 | python jaccard_minhash.py 7 |
k = 10で実行. kを増やすとMinHashによる近似Jaccard係数がJaccard係数に近づいていることが確認できます.
1 | $ python jaccard_minhash.py 10 |
データセットを生成します. 具体的には, 150個の集合をランダムに生成します. そして各集合の要素種類数を400とし, 各要素を含むかどうかは確率的に決定します. 用意したデータセットの20%をクエリ, 80%をDBとします.
1 | # sketchの個数 |
Jaccard係数が大きい上位20個の集合を正解データとします.
1 | # クエリとDBのJaccard係数を計算して値の昇順にソート |
kを100, 1000, 10000と変化させたときのPrecision Recall Curveを描画します. なお, PrecisionはInterpolated Precisionにしています.
1 | plotDataSets_x = [] |
実行結果です. まとめたコードはGitHubにあげています.
kを増やすほどカーブの右肩が座標(1, 1)に近づいていることが確認できます. 検索結果の精度が良くなっている(Min HashによるJaccard係数の近似値がJaccard係数に近づいている)ようです.
本記事ではLocality Sensitive HashingであるMin Hashを用いて, Jaccard係数を近似する実験を行いました. 近似していることの確認は, Min Hashで生成するハッシュ値の数kを増やしていくことにより, 正解(Jaccard係数で導出したデータ)をどれだけ当てられるかを, Min Hashによる近似値についてのPredision Recall Curveを作成することで確かめました.
結果の図より, kを増やすとカーブの右肩が座標(1,1)に近づくため, Jaccard係数で導出した結果に近づいていくことを確認できました.
Jaccard係数は集合間の類似度を表す尺度(値域は0から1の間)であり, (1)式により定義されます(値が1に近づくほど類似度が高い). ]]>
確率変数のある実現値xと, 別の確率変数のある実現値yに対して, 自己相互情報量PMI(x, y)は,
$PMI(x, y) = \log_2\frac{P(x, y)}{P(x)P(y)}$ ・・・(1)
と定義され, 値が大きければ大きいほどxとyの関連している度合いが強い.
2つの確率変数XとYに対し, 相互情報量MI(X, Y)は,
$MI(X, Y) = \sum_{x, y} P(x, y)\log_2\frac{P(x, y)}{P(x)P(y)}$ ・・・(2)
と定義され, 0以上の値をとる $^1$.
PMIの値が示す2つの事象の関連度合いは, 自然言語処理においては単語の共起性と捉えることができ, さらに単語の意味的な類似性と近似できる(2つの単語が一緒に起こりやすい場合は, 意味的にも関連しているだろうという直感に基づいて).
ここでxとyを単語とすると,
と表される.
式は以下の通り.
$PMI(x, y) = \log_2\frac{P(x, y)}{P(x)P(y)} = \log_2\frac{\frac{C(x, y)}{N}}{\frac{C(x)}{N} \cdot \frac{C(y)}{N}} = \log_2\frac{C(x, y) \cdot N}{C(x)C(y)}$ ・・・(3)
単語の出現確率は, 最尤推定で決定されることが多い $^2$.
各単語の出現確率が二項分布に従っているとし, 尤度から最尤推定によって単語の出現確率の式を導出している例はこちら
(1)式を変形すると, 以下のようになる.
$PMI(x, y) = \log_2\frac{P(x, y)}{P(x)P(y)} = \log_2\frac{P(x \cap y)}{P(x)P(y)} = \log_2\frac{P(x)P(y \vert x)}{P(x)P(y)} = \log_2\frac{P(y \vert x)}{P(y)} = \log_2{P(y \vert x)} - \log_2{P(y)}$ ・・・(4)
(4)式は, 単語xが出現したときに単語yが出現する確率について, y単体での出現確率を引いたものである.
つまりPMIの式は, “単語の共起確率から単語単体での出現確率の影響を差し引くことで, より正確に単語間の共起を測ろうとしている”と理解できる.
例えば, “the”のようにどの文中にも現れるような単語は, 他のどの単語に対しても共起確率$P(“the”, w)$が高くなるので, 結果としてPMIの値が大きくなり, 関連が高いと判断されてしまう.
これを防ぐために”the”単体での出現確率を差し引くことで, 他単語との関連度を低くできる.
単語総数が10000の文書内にある2つの単語”自然”と”言語”の関連度を測るとする.
文中に”自然”が120回, “言語”が40回出現し, 20回共起しているとすると(3)式より,
$PMI(自然, 言語) = \log_{2}\frac{20 \cdot 10000}{120 \cdot 40} \approx 5.38$
2つの単語が共起関係にあるかどうかの判定方法は, 単語Bigramや単語10-gramを使い, 単語xの前後2単語以内もしくは10単語以内に単語yが出現するかどうかで判定することがある $^3$$^,$$^4$.
つまり, 単語の共起確率は単語n-gramでの共起を基に求めるということ.
PMIには2つの問題があるため, 使う際には考慮する必要がある.
単語の共起頻度が0の場合,
$P(x,y) = 0$ ⇒ $PMI(x, y) = -∞$
と発散してしまい, PMIが計算できない問題がある.
この問題に対し, 共起頻度が0の時点でPMIの値を0とするか, もしくはPMI導出式の共起確率部分に一定のパラメータを加算するスムージング(平滑化)がよく行われる $^5$$^,$$^6$.
例として単語総数が10000の文書について, 以下の2つの場合を考える.
単語xとyがそれぞれ1000回出現していて, さらにそれが常に共起している2つ目のPMIの方が値は大きいはずだが, 1つ目の単語の出現頻度が低い場合のPMIの方が大きい.
この問題は単語の出現確率が最尤推定による推定量であることが関係しており, 最尤推定で求めた出現確率が単語の正確な出現確率と大きく異なっていることが原因であると考えられる.
この問題を解決するためには単語の出現確率をより正確に求めることが必要である.
したがって単語の出現確率に最尤推定量を使うのではなく, 事後分布最大化推定値(maximum a posterior estimator, MAP 推定値)などの他の推定手法を用いればよい.
[1] “例題:相互情報量の性質”.
http://omm.ishikawa-nct.ac.jp/ex/exercises/Pj0GgAAA/#answer
[2] “単語の出現確率の最尤推定”
http://id.fnshr.info/2012/01/24/wordmle/
[3] “FSNLP 5.4 Mutual Information(相互情報量)”
http://d.hatena.ne.jp/n_shuyo/20100827/fsnlp
[4] 鍋島啓太. “構成性に基づく評価極性知識獲得“. 2011.
[5] 岩成達哉ら. “多様な手がかりを用いた形容詞に基づく概念語の順序付け“. 2016.
[6] 仁科俊晴ら. “対義形容詞対との相互情報量を利用した概念語の順序付け“. 2013.
[7] Gang Guoら. “A COMPARATIVE STUDY ON VARIOUS CONFIDENCE MEASURES IN LARGE VOCABULARY SPEECH RECOGNITION“. 2004.
[8] 高村大也. “言語処理のための機械学習入門”. 2011.
※ [8]については1.6章と4.6章を参考にした.
]]>スマホでGoogle検索エンジンを使ってWeb検索すると, 検索結果画面はスマホの画面サイズにアレンジされたものとなって表示される. さらに検索結果件数が表示されなくなったりと, 表示そのものの簡単化も行われる.
自分はFirefox add-on for mobileの開発で, WebIDF (Web Inverse Document Frequency) を特徴量のひとつとして使っている関係上, 検索結果件数を取得できないことは致命傷. そこでGoogle検索結果画面のPC版表示がどうしても必要だったので, その対処法を示す.
検索結果画面のURL末尾にパラメータ&nomo=1
を付与する.
1 | https://www.google.co.jp/search?q=<検索クエリ>&nomo=1 |
これによってPC版の検索結果画面が表示される. そして検索結果件数が欲しいなら好みのやり方で#resultStatsを指定して取得するだけ.
Google検索結果画面の表示件数を変更したい場合にも上記方法は使える. Google検索エンジンの設定画面のURL末尾にパラメータ&nomo=1
を付与してアクセスすれば詳細な設定変更を行うことができる.
スマホでGoogle検索エンジンを使ってWeb検索すると, 検索結果画面はスマホの画面サイズにアレンジされたものとなって表示される. さらに]]>
半年前から先輩の誘いでJava講習の非常勤講師を”ときどき”やっている. そしてその講習会の特に初心者向けでは, 意外な質問が飛んできたり受講者分のエラーコードが見られたりするので, 結構楽しい.
そこで今回は質問の中で「ああそういえば」となった事を書き残す. それはJavaで識別子にUnicode文字が使えるよって(まぁ言語仕様の)お話.
Javaでは文字リテラルや文字列リテラル, 識別子(クラス, メソッド, 変数名)にUnicode文字を使うことができる. それらはUTF-8, UTF-16で書くもよし, エスケープ記法で書くもよし.
下記例は識別子をUTF-8で書いた場合のプログラムである. これをExample.javaとして保存し, コンパイルしてから実行すればちくわと最も相性のいい”きゅうり”を表示する!…はず!!
1 | public class Example { |
上記例で示したことはJavaの言語規定にしっかりと記されている(Java言語規定 - 3.8識別子).
これを応用?した面白い例を紹介します.
1 | \u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020 |
!?
!?!?
自分はUnicodeマスターではないため分かりませんでしたが, 実行すると”Hello world”と表示します. これはUgly.javaとして保存すればちゃんと扱えるプログラムなのです.
下記は実際にこれをマルチバイト文字に変換したものです.
1 | public class Ugly { |
ちなみに, このおまけはJava PuzzlersというJavaの問題集に載っています. 難しいけどオススメです.
]]>半年前から先輩の誘いでJava講習の非常勤講師を”ときどき”やっている. そしてその講習会の特に初心者向けでは, 意外な質問が飛んできたり受]]>
TensorFlowでRNNのPTB LSTM model(ptb_word_lm.py)の実行を試みたところ, エラーになったので, その対処をしました.
カレントディレクトリはptb_word_lm.pyがある階層です.
GitHubのissue #1121によると, ズバリ0.6.0を使ってくれとのことでした. よって今のところはブランチを0.6.0に変えて解決です(vrvさんによると現在バグ対処してくれている模様です).
ptb_word_lm.pyのfrom tensorflow.models.rnn.ptb import reader
をimport reader
にして解決です.
futureのdivisionモジュールでの割り算でエラーが出ています.
これはPython 3系を使っていれば起こらないエラーのようです. 自分は2.7系なのでfrom __future__ import division
をコメントアウトして解決です.
timeモジュールが見つからないためエラーが出ています.import time
を追加して解決です.
トピックモデルとは, 文書集合から特徴(話題)を抽出する手法です. また, それぞれの文書が持つ話題を抽出したり, 文書間の類似性を抽出することが可能な手法です.
そこで今回は, トピックモデルを用いてニュース記事を対象に話題を抽出する実験を行ってみます. なお, トピックモデルの中でも特に潜在意味解析(latent semantic analysis, LSA)を実際に用いています(正確には, LSAはベクトル空間モデルであり, このLSAを確率モデルに拡張した確率的潜在意味解析がトピックモデルです. しかし, トピックモデルのカテゴリ(離散値確率)分布は, LSAで用いる行列分解手法として捉えることができるので, 僕は同じ物として扱っています).
LSAは, 文書集合データを(文書総数 x 単語数)の行列Nで表現したときに, 掛け合わせることで再び元の行列を構築できるような, 小さな(低ランクの)2つの行列を見つけることをします(これを非負値行列因子分解と言います). このとき, それぞれの行列は特徴の組み合わせで構築されているため, この特徴を用いることによって文書に潜在するトピック(文書を特徴づける単語)を抽出します.
NMFとは, 行列で表現した文字・画像・音声などのデータ(非負値行列)を指定する基底数に行列分解し, 低ランクの表現にすることによって, それぞれの行列が持つ潜在的な特徴を抽出することを可能にした行列低ランク近似手法です. 簡単に言えば, 非負値行列を2つの非負値行列に分解するアルゴリズムです. 以下, NMFについて詳細に説明します.
図1に示すように, 文書集合データを(D x V)の行列Nで表現するとします. ここでDは文書総数, Vは単語数です.
このNを分解すると, W(K x D)の基底行列とH(K x V)の特徴重みの行列となります. そしてこれらWとHを掛け合わせることにより, 元の行列Nの近似値が得られますが, 完全に一致する行列W, Hの値を求めるのは困難です.
そこで, この近似値を求める問題は, 行列Nと転置したWにHを掛け合わせた行列の距離を小さくする問題として扱うことができます. 具体的には, フロベニウスノルムの最小化を行います(図2).
フロベニウスノルムの最小化問題には, 模擬アニーリングや勾配降下法, 乗法的更新ルールなど, いくつかの数学的解法が提案されていますが, NMFで最も用いられる解法は乗法的更新ルールです(参考論文).
乗法的更新ルールの式を図3に示します.
そして, フロベニウスノルムの最小化問題を解くために, NMFでは行列WとHをランダムな値で初期化し, 乗法的更新ルールの式にしたがってWとHを更新して距離が収束するまでこの処理を繰り返します. NMFはこのようにして行列の低ランク近似を行います.
1 | feedlist=['http://rss.allabout.co.jp/aa/latest/ch/health/', |
1 | def stripHTML(h): |
1 | def separatewords(text): |
1 | def getarticlewords(): |
1 | def makematrix(allw,articlew): |
1 | $ python |
特徴行列が得られたので, NMFによって文書の持つ潜在的な特徴を抽出します.
1 | def difcost(a,b): |
1 | def factorize(v,pc=10,iter=50): |
1 | def showfeatures(w,h,titles,wordvec,out='features.txt'): |
1 | >>> import nmf |
結果を見ると, それぞれのトピック(各トピックにおいて出現確率が最も高い単語)ごとに関連した単語が抽出できていることが確認できます. 特にトピック13の「チョコ」なんかは, 「バレンタイン 褒美 自分 ショコラ チョコレート」のように, チョコととても関係の深い単語が抽出できていることがわかります.
さらに, そのトピックを持つ上位3件の文書を実際に確認したところ, トピック13のチョコにおいては, 「スリーエフのバレンタイン2016!ご褒美チョコ&感謝チョコ厳選5 」「ファミマのバレンタイン2016!あの人気店の味&本格チョコ厳選7 」「自分へのご褒美チョコレート2016厳選バレンタイン」の全てが, まさにチョコな記事でした.
最後に, 各文書が持つ潜在的なトピックを確認します. 下記コードでは, 各文書に潜在するトピックの上位3つを表示するとともに, 結果をファイルに書き出します.
1 | def showarticles(titles,toppatterns,patternnames,out='article.txt'): |
それでは実行してみます.
1 | >>> nmf.showarticles(artt,topp,pn) |
全文書(340記事)について, 各文書の持つ潜在的なトピック上位3つを表示しました. なお, プログラムと同階層に結果を書き出したファイルarticle.txtが生成されます.
実際に, 340個目の文書「東京から日帰りで行ける、無料でも十分に楽しめる見学施設」を確認したところ, 抽出されたトピック通り, 「冬」に行って楽しめる「人気」の施設を「紹介」している記事でした. トピックをうまく抽出できているようです. 文書の持つ話題を的確に捉えていることが確認できました.
それではNMFの実装コードをまとめたファイルもGitHubにあげておきます.
本記事ではトピックモデルの一種であるLSAを用いることによって, 多くのニュース記事から話題になっているトピックを抽出しました. また, 各文書が持つ潜在的な話題の抽出も行いました.
結果より, 文書が持つ話題を的確に抽出していることが確認できましたが, 「紹介」というトピックや, トピックにおいて出現確率の高い単語として「今回」「現在」のような副詞可能単語を確認しました. これらの単語は一般性が高いため, その文書を特徴づける単語としては適切ではありません. ゆえに, 文書の特徴の把握が難しくなります. したがって, 形態素解析によって形態素単位で分かち書きする処理の過程において, 「サ変接続」と「副詞可能」な単語も除外することによって, 把握が比較的容易なトピックの抽出が行えます.
本記事では各トピックにおける出現確率の高い6個の単語の中で, 確率が最も高い単語をトピックであるかのように扱いましたが, 正しくはこの単語6個すべてが1つのトピック(話題のクラスタ)であり, そして話題クラスタごとに関連した単語が6個抽出されている状態です.
実際のところ, この話題クラスタが”何のトピック”であるかの決定は, 本人の裁量で行うができます. したがって, 本記事では話題クラスタの中で出現確率が最も高い単語をトピックとしました.
この辺りの話については, アイスクリーム屋さんで統計学がわかる -回帰分析・因子分析編- が分かりやすいので, 一読することをすすめます.
トピックモデルの基本についてはトピックモデルの4章を参考にしました. 実装には集合知プログラミングの10章を参考にしました.
最後に, トピックモデルについてまとめたスライド集(ALBERT Official Blog -『トピックモデルによる統計的潜在意味解析』読書会を開催中です)を見つけたのでリンクしておきます.
]]>トピックモデルとは, 文書集合から特徴(話題)を抽出する手法です. また, それぞれの文書が持つ話題を抽出したり, 文書間の類似性を抽出する]]>
インターンネットとは、現場で実際に行われているWebビジネスに携わることができるテレビ朝日のインターンシップで、今年が初めての試みのようです。自分が調べた限りでは、テレビ放送局が行うインターンで「テレビ×Web」の仕事を実際に経験させてくれるのはここしかないと思います。
また、このインターンなんと、賞金(予算)として1,000,000円が与えられます。これも他では無いでしょう。
まとめると、このインターンネットは、1,000,000円の予算でWebサービスを作る活動のことです。
なお、活動は2種類「現行サービスの改善策提案型」と「新規サービスの提案型」があります。応募するにあたってどちらに取り組むかまでは決める必要はありませんが、自分の中でこんなことがあったらオモシロイ、とか絶対これがあれば便利、と言ったアイデアをたくさん持って参加することをおすすめします。
また、応募はグループ応募と個人応募があります。個人で参加した場合は当日にグループを作ることができるので、問題ありません。ただし、あらかじめ自分の役割を明確にしておくことをおすすめします。
この活動の目的は、勝ち取った予算でWebサービスを作り、それを世に発信することです。0を1に、1を10に、全部自分の手で行うことができます。
したがって、活動は2段階に分かれます。1段目は予算を取る活動、2段目は提案サービスをカタチにする活動です。
前者は参加者全員取り組むことができますが、後者は勝ち取った者のみ取り組むことができます。
予算を取るためには、先進のスタッフの前でめっちゃくちゃ良いプレゼンをして、自分の提案を認めてもらう必要があります。
そのため、市場や現行サービスの問題点を含めた状況把握、どれくらいのお金が動くのかor得られそうかを含めたマネタイズ、そして解決策と言った材料が必要になります。
そこで本インターンでは、めっちゃくちゃ良いプレゼンをするために、材料を揃えるための方法論を教わります。
今回教わったのはデザインスプリントです。デザインスプリントの詳細は本記事では割愛しますが、簡単に説明すると、デザインスプリントは仮想の人間を作り、その人に提供する価値を考える方法です。
この活動ではデザインスプリントを通して得られた材料をスライドに反映して、それを発表します。
ここで提案が認められると、次のステップへ進むことができます。
ここからはメンター2, 3人の監督の下、提案サービスをカタチにしていきます。
活動スタイルですが、基本的には1ヶ月の間グループで自主的に作業し、週1日の頻度で行う局内でのメンターとの会合にて進捗を報告し、また問題点等の共有を行います。なお、状況によっては活動期間が変動する場合があります。
活動はグループで行いますが、基本的な作業は個々人の役割に沿って行います。
活動内容の詳細はまだ公開できませんが、ここで言えることは実に濃い内容の有意義な活動ができます。
本インターンシップは、テレビ放送局の性質上、他業種・他職種の人と関わる機会が多く存在するため、多様な意見をもらいながらWebサービスを作ることができます。技術力はもちろん、考え方の成長も感じられることでしょう。
]]>1 | var Request = require("sdk/request").Request; |
– ちなみにレスポンスで受ける文字列が化けるのでcharsetはutf-8にするといい
]]>下記はブラウザのメニューバーにアイコンを追加し, それをクリックすると新しいタブを開いてモジラのページへジャンプするコード
1 | var buttons = require('sdk/ui/button/action'); |
$ hexo new "hoge"
source/_posts 配下に .md ファイルが生成される
$ hexo server
$ hexo deploy -g
これは generate の役目も担う