他のブログを参考にすると関連記事というのを最後に表示しているらしい。
まだ記事の数は少ないけど、ある程度近い記事を探すことができるようにする。
"MeCab" + "類似" でググったら、コサイン類似度なるものがあるとのこと。
このブログは js + hugo で書いているので、 kuromoji のラッパー kuromojin を使って形態素解析する。
テキストを分解して、 単語と出現回数を記録する。
async function parseVector(text) {
const tokens = await tokenize(text)
return tokens.reduce((acc, next) => {
if (!isTargetToken(next)) {
return acc
}
if (acc[next.surface_form]) {
acc[next.surface_form]++
} else {
acc[next.surface_form] = 1
}
return acc
}, {})
}
出現回数を数えるのは名詞で 3 文字以上の単語のみ。
function isTargetToken(token) {
return (
token.pos === '名詞' &&
token.surface_form.length >= 3 &&
(token.basic_form !== '*' || token.surface_form.match(/^[\wA-Z]+$/))
)
}
あとは計算しておしまい。
function cosineSimilarity(curr, next) {
const currKeys = Object.keys(curr)
const nextKeys = Object.keys(next)
const keys = currKeys
.concat(nextKeys)
.filter((v, i, self) => self.indexOf(v) === i)
const baseScore = keys
.map(k => (curr[k] || 0) * (next[k] || 0))
.reduce((acc, c) => acc + c, 0)
const currScore = keys
.map(k => (curr[k] ? Math.pow(curr[k], 2) : 0))
.reduce((acc, c) => acc + c, 0)
const nextScore = keys
.map(k => (next[k] ? Math.pow(next[k], 2) : 0))
.reduce((acc, c) => acc + c, 0)
const score = baseScore
? baseScore / (Math.sqrt(currScore) * Math.sqrt(nextScore))
: 0
const words = keys.filter(k => curr[k] && next[k])
return { score, words }
}
うーん、全然関係ない単語でしか一致しない。その通りではあるんだけど。
babel-node source/js/similarity "content/posts/js/cosine-similarity.md" "content"
25.26% content\posts\2016-05-23\github-cli.md com, https, mijime, github
0% content\posts\2016-05-26\border-implement.md
0% content\posts\2016-05-26\ci-is-difficult.md
0% content\posts\blog\first-blogged.md
0% content\posts\hugo\code-mermaid.md
20.9% content\posts\hugo\hugo-deploy.md branch, content
100% content\posts\js\cosine-similarity.md 100, 2016, 89571, MeCab, コサイン
9.93% content\posts\blog\blog-of-policy.md branch, posts, slides
1.98% content\posts\hugo\hugo-pagination.md html, pagination
3.52% content\slides\2016-05-27\my-first-slide.md html