推荐引擎
推荐引擎意在把最需要的推荐给用户。
在不同的机器学习场景中通常需要分析相似样本。而统计相似样本的方式可以基于欧氏距离分数,也可基于皮氏距离分数。
欧氏距离分数
欧
氏
距
离
分
数
=
1
1
+
欧
氏
距
离
欧氏距离分数 = \frac{1}{1+欧氏距离}
欧氏距离分数=1+欧氏距离1
计算所得欧氏距离分数区间处于:[0, 1],越趋于0样本间的欧氏距离越远,样本越不相似;越趋于1,样本间的欧氏距离越近,越相似。
构建样本之间的欧氏距离得分矩阵:
$$
\left[
\begin{array}{c}
& a & b & c & d & … \
a & 1 & 0.2 & 0.3 & 0.4 & … \
b & 0.2 & 1 & x & x & … \
c & 0.3 & x & 1 & x & … \
d & 0.4 & x & x & 1 & … \
… & … & … & … & … & … \
\end{array}
\right]
$$
案例:解析ratings.json,根据每个用户对已观看电影的打分计算样本间的欧氏距离,输出欧氏距离得分矩阵。
import json
import numpy as np
with open('../data/ratings.json', 'r') as f:
ratings = json.loads(f.read())
users, scmat = list(ratings.keys()), []
for user1 in users:
scrow = []
for user2 in users:
movies = set()
for movie in ratings[user1]:
if movie in ratings[user2]:
movies.add(movie)
if len(movies) == 0:
score = 0
else:
x, y = [], []
for movie in movies:
x.append(ratings[user1][movie])
y.append(ratings[user2][movie])
x = np.array(x)
y = np.array(y)
score = 1 / (1 + np.sqrt(((x - y) ** 2).sum()))
scrow.append(score)
scmat.append(scrow)
users = np.array(users)
scmat = np.array(scmat)
for scrow in scmat:
print(' '.join('{:.2f}'.format(score) for score in scrow))
皮尔逊相关系数
A = [1,2,3,1,2]
B = [3,4,5,3,4]
m = np.corrcoef(A, B)
皮尔逊相关系数 = 协方差 / 标准差之积
相关系数处于[-1, 1]区间。越靠近-1代表两组样本反相关,越靠近1代表两组样本正相关。
案例:使用皮尔逊相关系数计算两用户对一组电影评分的相关性。
score = np.corrcoef(x, y)[0, 1]
按照相似度从高到低排列每个用户的相似用户
# scmat矩阵中每一行为 每一个用户对所有用户的皮尔逊相关系数
for i, user in enumerate(users):
# 拿到所有相似用户与相似用户所对应的皮尔逊相关系数
sorted_indices = scmat[i].argsort()[::-1]
sorted_indices = sorted_indices[sorted_indices != i]
similar_users = users[sorted_indices]
similar_scores = scmat[i, sorted_indices]
print(user, similar_users, similar_scores, sep='\n')
生成推荐清单
- 找到所有皮尔逊系数正相关的用户
- 遍历当前用户的每个相似用户,拿到相似用户看过但是当前用户没有看过的电影作为推荐电影
- 多个相似用户有可能推荐同一部电影,则取每个相似用户对该电影的评分得均值作为推荐度。
- 可以把相似用户的皮尔逊系数作为权重,皮尔逊系数越大,推荐度越高。
# 找到所有皮尔逊系数正相关的用户
positive_mask = similar_scores > 0
similar_users = similar_users[positive_mask]
# 相似用户对应的皮尔逊相关系数
similar_scores = similar_scores[positive_mask]
#存储对于当前用户所推荐的电影以及电影的推荐度(推荐电影的平均分)
recomm_movies = {}
#遍历当前用户的每个相似用户
for i, similar_user in enumerate(similar_users):
#拿到相似用户看过但是当前用户没有看过的电影
for movie, score in ratings[similar_user].items():
if (movie not in ratings[user].keys()):
if movie not in recomm_movies:
recomm_movies[movie] = []
else:
recomm_movies[movie].append(score)
print(user)
movie_list = sorted(recomm_movies.items(), key=lambda x:np.average(x[1]), reverse=True)
print(movie_list)
自然语言处理(NLP)
Siri的工作流程:1. 听 2.懂 3.思考 4.组织语言 5.回答
- 语音识别
- 自然语言处理 - 语义分析
- 逻辑分析 - 结合业务场景与上下文
- 自然语言处理 - 分析结果生成自然语言文本
- 语音合成
自然语言处理的常用处理过程:
地板干净,床头整洁,不错。
房间价格不贵,还干净,强烈推荐。
什么破地方,要啥啥没有,真垃圾,再也不来了。
先针对训练文本进行分词处理(词干提取、原型提取),统计词频,通过词频-逆文档频率算法获得该词对样本语义的贡献,根据每个词的贡献力度,构建有监督分类学习模型。把测试样本交给模型处理,得到测试样本的语义类别。
自然语言工具包 - NLTK
文本分词
分词处理相关API:
import nltk.tokenize as tk
# 把样本按句子进行拆分 sent_list:句子列表
sent_list = tk.sent_tokenize(text)
# 把样本按单词进行拆分 word_list:单词列表
word_list = tk.word_tokenize(text)
# 把样本按单词进行拆分 punctTokenizer:分词器对象
punctTokenizer = tk.WordPunctTokenizer()
word_list = punctTokenizer.tokenize(text)
案例:
import nltk.tokenize as tk
doc = "Are you curious about tokenization? " \
"Let's see how it works! " \
"We need to analyze a couple of sentences " \
"with punctuations to see it in action."
print(doc)
tokens = tk.sent_tokenize(doc)
for i, token in enumerate(tokens):
print("%2d" % (i + 1), token)
print('-' * 15)
tokens = tk.word_tokenize(doc)
for i, token in enumerate(tokens):
print("%2d" % (i + 1), token)
print('-' * 15)
tokenizer = tk.WordPunctTokenizer()
tokens = tokenizer.tokenize(doc)
for i, token in enumerate(tokens):
print("%2d" % (i + 1), token)
词干提取
文本样本中的单词的词性与时态对于语义分析并无太大影响,所以需要对单词进行词干提取。
词干提取相关API:
import nltk.stem.porter as pt
import nltk.stem.lancaster as lc
import nltk.stem.snowball as sb
stemmer = pt.PorterStemmer() # 波特词干提取器,偏宽松
stemmer = lc.LancasterStemmer() # 朗卡斯特词干提取器,偏严格
stemmer = sb.SnowballStemmer('english') # 思诺博词干提取器,偏中庸
r = stemmer.stem('playing') # 提取单词playing的词干
案例:
import nltk.stem.porter as pt
import nltk.stem.lancaster as lc
import nltk.stem.snowball as sb
words = ['table', 'probably', 'wolves', 'playing',
'is', 'dog', 'the', 'beaches', 'grounded',
'dreamt', 'envision']
pt_stemmer = pt.PorterStemmer()
lc_stemmer = lc.LancasterStemmer()
sb_stemmer = sb.SnowballStemmer('english')
for word in words:
pt_stem = pt_stemmer.stem(word)
lc_stem = lc_stemmer.stem(word)
sb_stem = sb_stemmer.stem(word)
print('%8s %8s %8s %8s' % (
word, pt_stem, lc_stem, sb_stem))
词性还原
与词干提取的作用类似,词性还原更利于人工二次处理。因为有些词干并非正确的单词,人工阅读更麻烦。词性还原可以把名词复数形式恢复为单数形式,动词分词形式恢复为原型形式。
词性还原相关API:
import nltk.stem as ns
# 获取词性还原器对象
lemmatizer = ns.WordNetLemmatizer()
# 把单词word按照名词进行还原
n_lemma = lemmatizer.lemmatize(word, pos='n')
# 把单词word按照动词进行还原
v_lemma = lemmatizer.lemmatize(word, pos='v')
案例:
import nltk.stem as ns
words = ['table', 'probably', 'wolves', 'playing',
'is', 'dog', 'the', 'beaches', 'grounded',
'dreamt', 'envision']
lemmatizer = ns.WordNetLemmatizer()
for word in words:
n_lemma = lemmatizer.lemmatize(word, pos='n')
v_lemma = lemmatizer.lemmatize(word, pos='v')
print('%8s %8s %8s' % (word, n_lemma, v_lemma))
词袋模型
一句话的语义很大程度取决于某个单词出现的次数,所以可以把句子中所有可能出现的单词作为特征名,每一个句子为一个样本,单词在句子中出现的次数为特征值构建数学模型,称为词袋模型。
The brown dog is running. The black dog is in the black room. Running in the room is forbidden.
1 The brown dog is running
2 The black dog is in the black room
3 Running in the room is forbidden
the | brown | dog | is | running | black | in | room | forbidden |
---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 1 | 0 | 2 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
词袋模型化相关API:
import sklearn.feature_extraction.text as ft
# 构建词袋模型对象
cv = ft.CountVectorizer()
# 训练模型,把句子中所有可能出现的单词作为特征名,每一个句子为一个样本,单词在句子中出现的次数为特征值。
bow = cv.fit_transform(sentences)
print(bow)
# 获取所有特征名
words = cv.get_feature_names()
案例:
import nltk.tokenize as tk
import sklearn.feature_extraction.text as ft
doc = 'The brown dog is running. ' \
'The black dog is in the black room. ' \
'Running in the room is forbidden.'
print(doc)
sentences = tk.sent_tokenize(doc)
print(sentences)
cv = ft.CountVectorizer()
bow = cv.fit_transform(sentences).toarray()
print(bow)
words = cv.get_feature_names()
print(words)
词频(TF)
单词在句子中出现的次数除以句子的总词数称为词频。即一个单词在一个句子中出现的频率。词频相比单词的出现次数可以更加客观的评估单词对一句话的语义的贡献度。词频越高,对语义的贡献度越大。对词袋矩阵归一化即可得到词频。
案例:对词袋矩阵进行归一化
import nltk.tokenize as tk
import sklearn.feature_extraction.text as ft
import sklearn.preprocessing as sp
doc = 'The brown dog is running. ' \
'The black dog is in the black room. ' \
'Running in the room is forbidden.'
print(doc)
sentences = tk.sent_tokenize(doc)
print(sentences)
cv = ft.CountVectorizer()
bow = cv.fit_transform(sentences).toarray()
print(bow)
words = cv.get_feature_names()
print(words)
tf = sp.normalize(bow, norm='l1')
print(tf)
文档频率(DF)
含有某个单词的文档样本数/总文档样本数
逆文档频率(IDF)
总样本数/含有某个单词的样本数
词频-逆文档频率(TF-IDF)
词频矩阵中的每一个元素乘以相应单词的逆文档频率,其值越大说明该词对样本语义的贡献越大,根据每个词的贡献力度,构建学习模型。
获取词频逆文档频率(TF-IDF)矩阵相关API:
# 获取词袋模型
cv = ft.CountVectorizer()
bow = cv.fit_transform(sentences).toarray()
# 获取TF-IDF模型训练器
tt = ft.TfidfTransformer()
tfidf = tt.fit_transform(bow).toarray()
案例:获取TF_IDF矩阵:
import nltk.tokenize as tk
import sklearn.feature_extraction.text as ft
doc = 'The brown dog is running. ' \
'The black dog is in the black room. ' \
'Running in the room is forbidden.'
print(doc)
sentences = tk.sent_tokenize(doc)
print(sentences)
cv = ft.CountVectorizer()
bow = cv.fit_transform(sentences).toarray()
print(bow)
words = cv.get_feature_names()
print(words)
tt = ft.TfidfTransformer()
tfidf = tt.fit_transform(bow).toarray()
print(tfidf)