# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %matplotlib inline
# %reload_ext autoreload
# %autoreload 2
# 多行输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all" 

# ## 博客
# Great NLP-related blogs:
# - [<NAME>](http://ruder.io/)
# - [<NAME>](https://jalammar.github.io/)
# - [<NAME>](http://www.abigailsee.com/)
# - [<NAME>](https://medium.com/@joycex99)
# - [<NAME>](https://smerity.com/articles/articles.html)
# - [<NAME>](https://towardsdatascience.com/evaluating-text-output-in-nlp-bleu-at-your-own-risk-e8609665a213)
#
# Other great technical blog posts:
# - [<NAME>](http://nbviewer.jupyter.org/url/norvig.com/ipython/ProbabilityParadox.ipynb) (more [here](http://norvig.com/ipython/))
# - [<NAME>](https://codewords.recurse.com/issues/five/why-do-neural-networks-think-a-panda-is-a-vulture) (more [here](https://jvns.ca/blog/2014/08/12/what-happens-if-you-write-a-tcp-stack-in-python/))
# - [<NAME>](http://blog.juliaferraioli.com/2016/02/exploring-world-using-vision-twilio.html)
# - [<NAME>](https://blog.slavv.com/picking-an-optimizer-for-style-transfer-86e7b8cba84b)
# - find [more on twitter](https://twitter.com/math_rachel)

# ### 处理流程
#
# [自然语言处理时，通常的文本清理流程是什么？ - 知乎](https://www.zhihu.com/question/268849350)
#
# 1. 获取语料库
# 2. 去除非文本信息
# 3. 拼写检查
# 4. 转化为小写，中文的编码 ，去掉标点符号，
# 5. Rare word replacement: 将词频小于5的替换成一个特殊的Token <UNK>
# 6. Add <BOS>, <EOS>：在句前，句后加入<BOS>和<EOS>。这个是个依赖具体模型（Model-Based）的处理策略。因为现在都用RNN，RNN的结构对于结尾不敏感（Sensitive），所以要加。
# 7. Long Sentence Cut-Off：将句长超过L的变成L。这个也是Model-Based，因为RNN需要Unrolling。
# 5. 词性标注与Named Entity Recognition
# 6. 词性还原与词干提取
# 7. 引入停用词
# 8. TF-IDF的特征处理
# 9. 建立分析模型

# ## NLP
#
# NLP 包含以下的任务:
#
# - 词性标注
# - 命名实体识别（人名、地名）
# - 问答系统 Question answering
# - 语音辨识 Speech recognition
# - 语音合成和语音识别 Text-to-speech and Speech-to-text
# - 主题模型 Topic modeling
# - 语义分类 Sentiment classification
# - 语言模型 Language modeling
# - 翻译 Translation

# +
# # !pip install -U nltk spacy gensim

# +
# # !sudo apt-get -y install protobuf-compiler libprotoc-dev

# +
# # !pip install pytext-nlp
# -

# ```bash
# $ git clone https://github.com/facebookresearch/fastText.git
# $ cd fastText
# $ make
# ```

# ## 基于矩阵分解的主题模型

# 最佳分解是将文档聚类成两组，组与组之间具有尽可能彼此不同的单词分布，但在组中的文档中尽可能相似。 我们将这两个组称为“主题”。 我们会根据每个主题中最常出现的词汇将这些词汇分为两组。

import numpy as np
from sklearn.datasets import fetch_20newsgroups
from sklearn import decomposition
from scipy import linalg
import matplotlib.pyplot as plt
from pathlib import Path
from dataclasses import dataclass

# newsgroups 数据集包括18,000个新闻组帖子，其中包含20个主题

# ### 获取数据集

root = Path('/home/lab/Datasets/scikit_learn_data')
root_nltk = Path('/home/lab/Datasets/nltk_data')

categories = ['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
remove = ('headers', 'footers', 'quotes')
newsgroups_train = fetch_20newsgroups(data_home=root, subset='train', categories=categories, remove=remove)
newsgroups_test = fetch_20newsgroups(data_home=root, subset='test', categories=categories, remove=remove)

newsgroups_train.filenames.shape # 样本名
newsgroups_train.target.shape
len(newsgroups_train.data)
# 2034 篇文章也即2034个样本

newsgroups_train.keys()

# - 样本真实内容

newsgroups_train.target[0]
newsgroups_train.filenames[0]
print(newsgroups_train.data[0])

# - 标签与标签名

newsgroups_train.target[0]

newsgroups_train.target_names[1]

# - 话题数与话题单词数

num_topics, num_top_words = 6, 10

# ### 停用词

# - 停用词 stop words 对文本的含义没有太大帮助的词，比如 the, 通常会被过滤掉[停用词 - 维基百科，自由的百科全书](https://zh.wikipedia.org/wiki/%E5%81%9C%E7%94%A8%E8%AF%8D)

from sklearn.feature_extraction import stop_words

dir(stop_words)

sorted(list(stop_words.ENGLISH_STOP_WORDS))[:20]  # 字典本身无序，转换成列表后排序

# - stop words 通常没有太多具体的含义
# - stop words 通常没有一个明确的停用词表能够适用于所有的工具

# ### 词干和词性还原 Stemming and Lemmatization
# - organize, organizes, and organizing 他们的词干是删除截尾的部分字母，最后的可能不是真正的单词，词性还原更加准确 cars -> car 名词还原
# - 词形还原（lemmatization），是把一个词汇还原为一般形式（能表达完整语义），方法较为复杂；而词干提取（stemming）是抽取词的词干或词根形式（不一定能够表达完整语义），方法较为简单
# - 不同语言不同

import nltk

# nltk.download('wordnet', download_dir=root_nltk)
nltk.download('wordnet')

# ### 词干提取

from nltk import stem

# - 基于Porter词干提取算法

wnl = stem.WordNetLemmatizer()
porter = stem.porter.PorterStemmer()

word_list = ['feet', 'foot', 'foots', 'footing']

[wnl.lemmatize(word) for word in word_list]

[porter.stem(word) for word in word_list]

wl = ['organize', 'organizes', 'organizing']

[wnl.lemmatize(word) for word in wl]

[porter.stem(word) for word in wl]

# - nltk 具有更多的方法，但是方法没有经过优化，Spacy 的方法较少，但是都是经过优化的

import spacy

from spacy.lemmatizer import Lemmatizer

# +
# lemmatizer = Lemmatizer()
# -

# - 下载英语模型

# +
# # !pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_md-1.2.0/en_core_web_md-1.2.0.tar.gz
# -

# - 词性还原

# +
# [lemmatizer.lookup(word) for word in word_list]
# -

# - spacy 不提供词干提取只提供词性还原，这也是一种优化，因为 词性提取是更准确的

# - 停用词 stop words 在各个库中也是不一样的

# +
# nlp = spacy.load("en_core_web_sm")

# +
# sorted(list(nlp.Defaults.stop_words))[:20]
# -

# 上述方法长期以来被认为是标准技术，但如果使用深度学习，它们通常会损害您的表现。 
#
# 词干化，词形还原和删除停用词都涉及丢弃信息。
#
# 但是，在处理更简单的模型时，它们仍然有用

# - 词频

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

nltk.download('punkt')

# +
from nltk import word_tokenize

@dataclass
class LemmaTokenizer:
    wnl = stem.WordNetLemmatizer()
    
    def __call__(self, doc):
        return [self.wnl.lemmatize(t) for t in word_tokenize(doc)]


# -

vectorizer = CountVectorizer(stop_words='english')

# - 向量化 - 统计词频

vectors = vectorizer.fit_transform(newsgroups_train.data).todense() # (文档数, 词汇量)

vectors.shape

vocab = np.array(vectorizer.get_feature_names())

vocab

vocab.shape

vocab[3456:4567]

# ## SVD

# 1. 我们显然希望在一个主题中出现频率最高的单词在另一个主题中出现频率较低，否则这个单词就不能很好地分离出这两个主题。因此，我们**希望主题是正交**
# 2. SVD算法将一个矩阵分解为一个正交列矩阵和一个正交行矩阵(以及一个对角矩阵，其中包含每个因子的相对重要性)
# 3. SVD 是精确分解
# - SVD的应用
#     - 语义分析
#     - 协同过滤/推荐(获得Netflix奖)
#     - 数据压缩
#     - 主成分分析

# %time U, s, Vh = linalg.svd(vectors, full_matrices=False)

np.diag(s)

U.shape
s.shape
Vh.shape

plt.plot(s)

plt.plot(s[:15])

s[:10]

num_topics, num_top_words = 6, 10


# ## 主题 topics

# +
def top_words(t):
    # 按 Vh 的数值对单词进行排序
    vs = [vocab[i] for i in np.argsort(t)]
    # 取出关键词，排名前几的，数值最大的
    # 按照从大到小的顺序
    kw = vs[:-num_top_words-1:-1]
    return kw
    
def show_topics(a):
    # 对每个文档计算排名靠前的词
    topic_words = [top_words(t) for t in a]
    return [' '.join(t) for t in topic_words]


# -

# - topic文章的关键词

show_topics(Vh[:num_topics])

# 与其限制因子正交，另一个想法是限制它们是非负的，**更容易解释，非精确分解**
#
# $$V = WH$$

num_topics = 6

clf = decomposition.NMF(n_components=num_topics, random_state=1)

vectors.shape
vectors

W1 = clf.fit_transform(vectors)
H1 = clf.components_

W1.shape
W1

H1.shape
H1

# - 两种方法的主题关键词对比
# - **SVD和NMF都可以获得主题与主题对应的关键词**

show_topics(H1)
show_topics(Vh[:num_topics])
# 有一个不重合

# ## 基于 TF-IDF 的主题模型

tf = TfidfVectorizer(stop_words='english')
tfidf = tf.fit_transform(newsgroups_train.data)

tfidf.toarray()

num_topics = 6
clf = decomposition.NMF(n_components=num_topics, random_state=1)

W1 = clf.fit_transform(tfidf)
H1 = clf.components_

show_topics(H1)

H1

plt.plot(H1[0])

plt.plot(sorted(H1[0]))

plt.plot(sorted(H1[0], reverse=True)[:12])

# - 基本上也就数值最大的10个词有很强的区分性

clf.reconstruction_err_

# - 可以基于 **词频** 进行主题建模也可以基于 **TF_IDF** 进行主题建模

# ## Truncated SVD

# %time u, s, v = np.linalg.svd(vectors, full_matrices=False)

# +
# # !pip install fbpca
# -

# [facebook/fbpca: Fast Randomized PCA/SVD](https://github.com/facebook/fbpca)

from sklearn.utils.extmath import randomized_svd
import fbpca

# Computes a truncated randomized SVD
# %time u, s, v = randomized_svd(tfidf, 10)

# %time u, s, v = fbpca.pca(tfidf, 10)

# - SVD和NMF都可以进行稀疏矩阵的分解



StarcoderdataJupyterScriptsDedupFiltered