AIGC 涉及的算法 - 否 - 自然语言处理算法 (NLP)
基本介绍
自然语言处理 (NLP) 是一个专注于计算机与人类语言之间交互的研究领域。它涉及教计算机以对人们有意义和有用的方式理解、解释和生成人类语言。NLP 是人工智能 (AI) 的一个子领域,近年来受到了极大的关注。
自然语言处理是一门融语言学、计算机科学、数学于一体的科学。因此,这一领域的研究将涉及自然语言,即人们日常使用的语言,所以它与语言学的研究有着密切的联系,但又有重要的区别。自然语言处理并不是一般地研究自然语言,而在于研制能有效地实现自然语言通信的计算机系统,特别是其中的软件系统。因而它是计算机科学的一部分。
定义与背景
NLP大模型是指通过大规模预训练和自监督学习技术构建的深度学习模型,旨在提高计算机对自然语言的理解和生成能力。这类模型通常具有数以亿计的参数,能够处理复杂的语言任务。其起源可以追溯到2017年,当时Google发布了Transformer模型,该模型为后续的NLP大模型发展奠定了基础。
基础技术
- 分词:基本算是所有NLP任务中最底层的技术。不论解决什么问题,分词永远是第一步。
- 词性标注:判断文本中的词的词性(名词、动词、形容词等等),一般作为额外特征使用。
- 句法分析:分为句法结构分析和依存句法分析两种。
- 词干提取:从单词各种前缀后缀变化、时态变化等变化中还原词干,常见于英文文本处理。
- 命名实体识别:识别并抽取文本中的实体,一般采用BIO形式。
- 指代消歧:文本中的代词,如“他”“这个”等,还原成其所指实体。
- 关键词抽取:提取文本中的关键词,用以表征文本或下游应用。
- 词向量与词嵌入:把单词映射到低维空间中,并保持单词间相互关系不变。是NLP深度学习技术的基础。
- 文本生成:给定特定的文本输入,生成所需要的文本,主要应用于文本摘要、对话系统、机器翻译、问答系统等领域。
NLP 的工作原理
NLP 涉及处理和分析人类语言的几个步骤。第一步是将文本分解为更小的单元,例如单词和短语。此过程称为分词化。文本被标记化后,下一步是为每个单词分配含义。此过程称为命名实体识别 (NER)。
NLP 的另一个重要方面是词性标记,它涉及识别句子中每个单词的语法功能。这对于理解句子的上下文和结构很有用。NLP 还涉及机器学习算法(如聚类和分类)来分析和分类文本数据。
NLP 的应用
NLP 在各个行业都有众多应用。最常见的应用之一是情绪分析,它涉及分析客户评论和社交媒体帖子以了解对产品或品牌的情绪。NLP 还用于聊天机器人和虚拟助手,以帮助用户以更自然的方式与机器交互。
NLP 的其他应用包括机器翻译、语音识别和文本摘要。NLP 还用于医疗保健,以分析电子病历并协助临床决策。在法律行业,NLP 用于电子取证和合同分析。
学习自然语言处理的步骤
- 学习编程和数据结构的基础知识。
- 学习 Python,这是 NLP 最流行的语言。
- 了解文本预处理技术,例如分词化、词干提取和非索引字删除。
- 了解文本表示技术,例如词袋和 TF-IDF。
- 了解流行的 NLP 库,例如 NLTK、spaCy 和 Gensim。
- 了解适用于 NLP 的机器学习算法,例如 Naive Bayes、Support Vector Machines 和 Random Forests。
- 了解 NLP 的深度学习技术,例如递归神经网络 (RNN) 和 Transformers。
- 了解常见的 NLP 任务,例如情绪分析、命名实体识别和机器翻译。
- 参与实践项目以应用您的知识并培养实践技能。
- 及时了解 NLP 的最新研究和趋势。
开始
对于此 NLP 教程,您需要安装 Python 以及本指南中使用的最流行的自然语言处理库。
打开终端并键入(可能需要一段时间才能运行):
pip install nltk
pip install -U scikit-learn
pip install gensim
pip install networkx
pip install googletrans==4.0.0-rc1
pip install graphviz
pip3 install chatterbot
pip install -U pip setuptools wheel
pip install -U spacy
python -m spacy download en_core_web_sm
什么是分词化
分词是将大文本分解为称为分词的小块的过程。这些标记通常是单词、短语或句子。标记化是许多自然语言处理 (NLP) 任务中的关键步骤。
以下是一些示例 Python 代码,用于说明使用流行的 NLTK 库进行分词:
import nltk
nltk.download('punkt')
text = "Tokenization is the process of breaking down a large text into smaller chunks called tokens. These tokens are usually words, phrases, or sentences."
tokens = nltk.word_tokenize(text)
print(tokens)
您将在输出中看到一个 Python 列表,其中句子被拆分为标记。
['Tokenization', 'is', 'the', 'process', 'of', 'breaking', 'down', 'a', 'large', 'text', 'into', 'smaller', 'chunks', 'called', 'tokens', '.', 'These', 'tokens', 'are', 'usually', 'words', ',', 'phrases', ',', 'or', 'sentences', '.']
可视化标记化。
import nltk
import matplotlib.pyplot as plt
nltk.download('punkt')
text = "Tokenization is the process of breaking down a large text into smaller chunks called tokens. These tokens are usually words, phrases, or sentences."
tokens = nltk.word_tokenize(text)
freq_dist = nltk.FreqDist(tokens)
freq_dist.plot(30)
此代码创建标记的频率分布,并在直方图中绘制 30 个最常见的标记。这种可视化可以帮助我们更好地理解文本的结构和内容。
什么是词性标记
词性 (POS) 标记是用相应的词性(例如名词、动词、形容词或副词)标记句子中的每个单词的过程。此信息用于许多自然语言处理任务,例如信息检索、机器翻译和情感分析。
以下是一些示例 Python 代码,用于说明使用流行的 NLTK 库进行 POS 标记:
import nltk
nltk.download('averaged_perceptron_tagger')
text = "Tokenization is the process of breaking down a large text into smaller chunks called tokens."
tokens = nltk.word_tokenize(text)
pos_tags = nltk.pos_tag(tokens)
print(pos_tags)
在输出中,您将看到一个令牌列表,但这次,POS 标签将作为 Python 元组与令牌一起添加。如果您不知道这些是什么意思,请务必阅读我们关于 nltk POS 标签的博客文章。
[('Tokenization', 'NN'), ('is', 'VBZ'), ('the', 'DT'), ('process', 'NN'), ('of', 'IN'), ('breaking', 'VBG'), ('down', 'RP'), ('a', 'DT'), ('large', 'JJ'), ('text', 'NN'), ('into', 'IN'), ('smaller', 'JJR'), ('chunks', 'NNS'), ('called', 'VBD'), ('tokens', 'NNS'), ('.', '.')]
可视化词性标记。
import nltk
import matplotlib.pyplot as plt
nltk.download('averaged_perceptron_tagger')
text = "Tokenization is the process of breaking down a large text into smaller chunks called tokens."
tokens = nltk.word_tokenize(text)
pos_tags = nltk.pos_tag(tokens)
pos_freq = {}
for tag in pos_tags:
pos = tag[1]
if pos in pos_freq:
pos_freq[pos] += 1
else:
pos_freq[pos] = 1
labels = pos_freq.keys()
sizes = pos_freq.values()
plt.pie(sizes, labels=labels, autopct='%1.1f%%')
plt.axis('equal')
plt.show()
此代码创建词性标签的频率分布,并将其绘制在饼图中。这种可视化可以帮助我们理解文本中不同词性的分布。
什么是词干提取
词干提取是将变形词减少到其基本或词根形式的过程,通常会导致创建可能不是实际单词的单词。例如,“jumping”、“jumps”、“jumped”在词干提取后都变成了“jump”。
在 Python 中,NLTK 库提供了一个可以执行词干提取的词干分析器模块。下面是一个示例代码,用于说明如何使用 Porter Stemmer 进行词干提取:
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = ["jumping", "jumps", "jumped"]
stemmed_words = [stemmer.stem(word) for word in words]
print(stemmed_words)
该代码生成一个输出,将列表的每个单词都显示到其词干版本中,这表明它们都具有相同的词干。
['jump', 'jump', 'jump']
可视化词干提取。此代码将生成一个条形图,显示每个词干的出现频率:
import matplotlib.pyplot as plt
word_freq = {"jumping": 5, "jumps": 3, "jumped": 2, "jumper": 1, "jumpers": 4, "jumpiest": 1, "jumpingly": 2, "jumpiness": 3}
stemmed_word_freq = {}
for word, freq in word_freq.items():
stemmed_word = stemmer.stem(word)
stemmed_word_freq[stemmed_word] = stemmed_word_freq.get(stemmed_word, 0) + freq
plt.bar(stemmed_word_freq.keys(), stemmed_word_freq.values())
plt.xlabel("Stemmed words")
plt.ylabel("Frequency")
plt.show()
输出显示每个字干的频率。
什么是情感分析
情感分析是从给定文本中识别和提取观点或情感的过程。它涉及将文本的极性分类为正、负或中性。这项技术有许多应用,包括市场研究、客户服务和社交媒体分析。
在 Python 中,NLTK 库提供了一个情感分析模块,该模块使用 VADER(Valence Aware Dictionary and sEntiment Reasoner)词典来执行情感分析。下面是一个示例代码,用于说明使用 VADER 模块进行情感分析:
import nltk
from nltk.sentiment import SentimentIntensityAnalyzer
nltk.download('vader_lexicon')
text = "I love this product! It's the best one I've ever used."
analyzer = SentimentIntensityAnalyzer()
scores = analyzer.polarity_scores(text)
print(scores)
polarity_scores() 方法返回一个字典,其中包含消极、中性、积极和复合情绪的分数。复合分数是一个范围从 -1(最负面)到 1(最正面)的指标。
{'neg': 0.0, 'neu': 0.479, 'pos': 0.521, 'compound': 0.8655}
为了可视化情绪评分,我们可以使用 Matplotlib 的饼图:
import matplotlib.pyplot as plt
labels = ['Negative', 'Neutral', 'Positive']
sizes = [scores['neg'], scores['neu'], scores['pos']]
colors = ['red', 'yellow', 'green']
plt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%')
plt.axis('equal')
plt.show()
什么是文本分类
文本分类是一种常见的 NLP 任务,其中给定的文本文档被分类为预定义的类别。例如,垃圾邮件检测是文本分类的一个示例,其中的任务是将电子邮件分类为垃圾邮件或非垃圾邮件。
下面是一个简短的 Python 代码来说明文本分类:
# Import the necessary libraries
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, confusion_matrix
# Load the dataset
newsgroups = fetch_20newsgroups(subset='all')
X = newsgroups.data
y = newsgroups.target
# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Vectorize the text data
vectorizer = TfidfVectorizer()
X_train_vect = vectorizer.fit_transform(X_train)
X_test_vect = vectorizer.transform(X_test)
# Train the model
model = MultinomialNB()
model.fit(X_train_vect, y_train)
# Predict on the test set
y_pred = model.predict(X_test_vect)
# Evaluate the performance
print('Accuracy:', accuracy_score(y_test, y_pred))
print('Confusion matrix:', confusion_matrix(y_test, y_pred))
上面的代码加载垃圾邮件数据集,预处理文本数据,使用 TfidfVectorizer 对文本数据进行矢量化,在训练集上训练 Naive Bayes 分类器,并在测试集上进行预测。最后,它使用准确性和混淆矩阵评估性能。
这个输出不是那么漂亮,我们可以使用该方法对其进行改进。plot_confusion_matrix
# Import the necessary libraries
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, confusion_matrix, plot_confusion_matrix
# Load the dataset
newsgroups = fetch_20newsgroups(subset='all')
X = newsgroups.data
y = newsgroups.target
# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Vectorize the text data
vectorizer = TfidfVectorizer()
X_train_vect = vectorizer.fit_transform(X_train)
X_test_vect = vectorizer.transform(X_test)
# Train the model
model = MultinomialNB()
model.fit(X_train_vect, y_train)
# Predict on the test set
y_pred = model.predict(X_test_vect)
# Evaluate the performance
print('Accuracy:', accuracy_score(y_test, y_pred))
print('Confusion matrix:')
plot_confusion_matrix(model, X_test_vect, y_test)
plt.show()
什么是语言建模
语言建模是预测给定前一个单词的序列中下一个单词的概率的任务。它涉及构建语言统计模型,该模型可用于生成文本或对文本进行预测。语言建模是许多自然语言处理任务的关键组成部分,例如语音识别、机器翻译和文本生成。
下面是一个简短的 Python 代码,用于使用自然语言工具包 (NLTK) 库训练简单的语言模型:
import nltk
from nltk.corpus import gutenberg
nltk.download('gutenberg')
# Load the text corpus
corpus = gutenberg.words('austen-emma.txt')
# Create a list of sentences
sentences = nltk.sent_tokenize(' '.join(corpus))
# Preprocess the sentences
preprocessed = []
for sentence in sentences:
tokens = nltk.word_tokenize(sentence.lower())
preprocessed.append(tokens)
# Train a bigram language model
model = nltk.BigramAssocMeasures()
finder = nltk.BigramCollocationFinder.from_documents(preprocessed)
finder.apply_freq_filter(5)
bigrams = finder.nbest(model.pmi, 20)
# Print the most frequent bigrams
print(bigrams)
此代码从 NLTK 库加载文本语料库,通过对句子进行分词和小写对其进行预处理,并使用逐点互信息 (PMI) 度量训练二元语法语言模型。然后打印最常见的二元语法。
[('brunswick', 'square'), ('sore', 'throat'), ('mill', 'farm'), ('william', 'larkins'), ('baked', 'apples'), ('box', 'hill'), ('sixteen', 'miles'), ('maple', 'grove'), ('hair', 'cut'), ('south', 'end'), ('colonel', 'campbell'), ('protest', 'against'), ('robert', 'martin'), ('vast', 'deal'), ('five', 'couple'), ('ready', 'wit'), ('musical', 'society'), ('infinitely', 'superior'), ('donwell', 'abbey'), ('married', 'women')]
为了可视化语言模型,我们可以使用 NetworkX 库创建二元语法的网络图:
import networkx as nx
import matplotlib.pyplot as plt
# Create a network graph of the bigrams
graph = nx.Graph()
for bigram in bigrams:
graph.add_edge(bigram[0], bigram[1])
# Draw the network graph
pos = nx.spring_layout(graph)
nx.draw_networkx_nodes(graph, pos, node_size=200, node_color='lightblue')
nx.draw_networkx_edges(graph, pos, width=1, alpha=0.5, edge_color='gray')
nx.draw_networkx_labels(graph, pos, font_size=10, font_family='sans-serif')
plt.axis('off')
plt.show()
什么是机器翻译
机器翻译是自然语言处理的一个子领域,专注于使用机器学习算法自动将文本从一种语言翻译成另一种语言。它涉及处理输入文本,生成其含义的表示形式,然后使用该表示形式以另一种语言生成相应的文本。
为了说明 Python 中的机器翻译,我们可以使用 Googletrans 库,它为 Google Translate API 提供了一个简单的接口。下面是一个使用 Googletrans 将句子从英语翻译成中文的示例代码:
from googletrans import Translator
translator = Translator(service_urls=['translate.google.com'])
text = "Hello, how are you today?"
translation = translator.translate(text, dest='CN')
print(translation.text)
翻译输出:
你好,你今天怎么样?
什么是 NLP 中的问答?
问答 (QA) 是一项 NLP 任务,其目标是为以自然语言提出的问题找到答案。这项任务涉及语言理解和信息检索技术。
为了说明 Python 中的问答,我们可以使用自然语言工具包 (NLTK) 库。下面是一个示例:
import nltk
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('stopwords')
from nltk.tokenize import word_tokenize, sent_tokenize
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
# Define the passage to search for answers
passage = "The quick brown fox jumps over the lazy dog. The dog, who was not very impressed by the fox, barked loudly and chased it away."
# Tokenize the passage into sentences
sentences = sent_tokenize(passage)
# Define the question to answer
question = "What did the dog do to the fox?"
# Tokenize the question
question_tokens = word_tokenize(question)
# Remove stopwords from the question
stopwords = set(stopwords.words('english'))
question_tokens = [word for word in question_tokens if word.lower() not in stopwords and word.isalpha()]
# Lemmatize the question tokens
lemmatizer = WordNetLemmatizer()
question_tokens = [lemmatizer.lemmatize(token) for token in question_tokens]
question_tokens = [x for x in question_tokens if '?' not in x]
# Find the sentence that contains the answer
answer = None
for sentence in sentences:
sentence_tokens = word_tokenize(sentence)
sentence_tokens = [word for word in sentence_tokens if word.lower() not in stopwords]
sentence_tokens = [lemmatizer.lemmatize(token) for token in sentence_tokens]
if set(question_tokens).issubset(set(sentence_tokens)):
answer = sentence
break
# Print the answer or a message if the answer cannot be found
if answer:
print(answer)
else:
print("Sorry, I couldn't find the answer to that question in the given passage.")
输出显示答案:
The quick brown fox jumps over the lazy dog.
此代码采用一段文本和一个问题,并尝试在段落中找到问题的答案。它将段落分词化为句子,并将问题分词化,删除停用词,并对分词进行词形还原。然后,它会搜索每个句子以查找与问题标记的匹配项,并打印包含答案的句子。
什么是信息提取
信息提取 (IE) 是从非结构化或半结构化数据源中自动提取结构化信息的过程。IE 通常涉及从文本数据中识别命名实体(例如,人员、地点、组织)以及它们之间的关系。
在 Python 中,一种流行的信息提取库是 spaCy。以下是使用 spaCy 从示例文本中提取命名实体的示例:
import spacy
nlp = spacy.load("en_core_web_sm")
text = "John Smith is a software engineer at Google in New York."
doc = nlp(text)
for ent in doc.ents:
print(ent.text, ent.label_)
John Smith PERSON
Google ORG
New York GPE
可视化输出。
from spacy import displacy
displacy.render(doc, style="ent", jupyter=True)
NLP 的未来
NLP 是一个快速发展的领域,仍有许多需要发现和改进的地方。随着机器学习算法变得越来越先进,NLP 有望变得更加准确和高效。NLP 也有望在教育、营销和客户服务等领域发挥越来越重要的作用。
更复杂的聊天机器人和虚拟助手的开发也有望推动 NLP 的增长。随着机器越来越擅长理解人类语言,它们将能够为用户提供更加个性化和有意义的交互。
Python 中的 NLP 项目示例
示例 1:标记化、POS 标记和情绪分析
import nltk
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
from nltk.sentiment import SentimentIntensityAnalyzer
text = "I am really excited about this new natural language processing project!"
tokens = word_tokenize(text)
pos_tags = pos_tag(tokens)
sentiment = SentimentIntensityAnalyzer().polarity_scores(text)
print("Tokens:", tokens)
print("POS tags:", pos_tags)
print("Sentiment:", sentiment)
输出:
Tokens: ['I', 'am', 'really', 'excited', 'about', 'this', 'new', 'natural', 'language', 'processing', 'project', '!']
POS tags: [('I', 'PRP'), ('am', 'VBP'), ('really', 'RB'), ('excited', 'VBN'), ('about', 'IN'), ('this', 'DT'), ('new', 'JJ'), ('natural', 'JJ'), ('language', 'NN'), ('processing', 'NN'), ('project', 'NN'), ('!', '.')]
Sentiment: {'neg': 0.0, 'neu': 0.593, 'pos': 0.407, 'compound': 0.6689}
示例 2:命名实体识别和文本分类
import nltk
from nltk.tokenize import word_tokenize
from nltk import pos_tag, ne_chunk
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('maxent_ne_chunker')
nltk.download('words')
text = "Steve Jobs was the CEO of Apple. Apple is located in California."
tokens = word_tokenize(text)
pos_tags = pos_tag(tokens)
named_entities = ne_chunk(pos_tags)
corpus = ["Steve Jobs was the CEO of Apple.", "Microsoft is located in Washington."]
labels = ["Apple", "Microsoft"]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
clf = MultinomialNB().fit(X, labels)
classification = clf.predict(vectorizer.transform([text]))
print("Named entities:", named_entities)
print("Classification:", classification)
输出:
Named entities: (S
(PERSON Steve/NNP)
(PERSON Jobs/NNP)
was/VBD
the/DT
(ORGANIZATION CEO/NNP)
of/IN
(GPE Apple/NNP)
./.
(PERSON Apple/NNP)
is/VBZ
located/VBN
in/IN
(GPE California/NNP)
./.)
Classification: ['Apple']
用于自然语言处理 (NLP) 的有用 Python 库
- 使用 nltk 的 NLP:word_tokenize、pos_tag、ne_chunk、sent_tokenize、wordnet、NaiveBayesClassifier、DecisionTreeClassifier
- 具有空间的 NLP:load、tokenizer、tagger、parser、ner、displacy
- 使用 gensim 的 NLP:语料库。字典、模型。Word2Vec,模型。Lda模型
- 带有 textblob 的 NLP:TextBlob、Word、Blobber、Sentiment、TextBlobDE、TextBlobFR
- 使用 scikit-learn 的 NLP:CountVectorizer、TfidfVectorizer、SVC、KNeighborsClassifier、RandomForestClassifier
常见问题解答
什么是 NLP?
分析、理解和生成人类语言。
NLP 有哪些应用?
情感分析、聊天机器人、机器翻译、语音识别等。
NLP 面临哪些挑战?
歧义、上下文、成语、俚语等。
NLP 中常用的技术有哪些?
令牌化、POS 标记、解析、信息提取等。
什么是 NLP 中的深度学习?
基于神经网络的文本分类、机器翻译等模型。
有哪些流行的 NLP 库?
NLTK、spaCy、Gensim、Transformers 等。
上一篇: 如何遍历 python 迭代器
推荐阅读
-
AIGC 涉及的算法 - 否 - 自然语言处理算法 (NLP)
-
计算机毕业设计 Hadoop+Spark 抖音可视化 抖音舆情监测 预测算法 抖音爬虫 抖音大数据情感分析 NLP 自然语言处理 Hive 机器学习 深度学习
-
【摩尔线程+Colossal-AI强强联手】MusaBert登上CLUE榜单TOP10:技术细节揭秘 - 技术实力:摩尔线程凭借"软硬兼备"的技术底蕴,让MusaBert得以从底层优化到顶层。其内置多功能GPU配备AI加速和并行计算模块,提供了全面的AI与科学计算支持,为AI推理和低资源条件下的大模型训练等场景带来了高效、经济且环保的算力。 - 算法层面亮点:依托Colossal-AI AI大模型开发系统,MusaBert在训练过程中展现出了卓越的并行性能与易用性,特别在预处理阶段对DataLoader进行了优化,适应低资源环境高效处理海量数据。同时,通过精细的建模优化、领域内数据增强以及Adan优化器等手段,挖掘和展示了预训练语言模型出色的语义理解潜力。基于MusaBert,摩尔线程自主研发的MusaSim通过对比学习方法微调,结合百万对标注数据,MusaSim在多个任务如语义相似度、意图识别和情绪分析中均表现出色。 - 数据资源丰富:MusaBert除了自家高质量语义相似数据外,还融合了悟道开源200GB数据、CLUE社区80GB数据,以及浪潮公司提供的1TB高质量数据,保证模型即便在较小规模下仍具备良好性能。 当前,MusaBert已成功应用于摩尔线程的智能客服与数字人项目,并广泛服务于语义相似度、情绪识别、阅读理解与声韵识别等领域。为了降低大模型开发和应用难度,MusaBert及其相关高质量模型代码已在Colossal-AI仓库开源,可快速训练优质中文BERT模型。同时,通过摩尔线程与潞晨科技的深度合作,仅需一张多功能GPU单卡便能高效训练MusaBert或更大规模的GPT2模型,显著降低预训练成本,进一步推动双方在低资源大模型训练领域的共享目标。 MusaBert荣登CLUE榜单TOP10,象征着摩尔线程与潞晨科技联合研发团队在中文预训练研究领域的领先地位。展望未来,双方将携手探索更大规模的自然语言模型研究,充分运用上游数据资源,产出更为强大的模型并开源。持续强化在摩尔线程多功能GPU上的大模型训练能力,特别是在消费级显卡等低资源环境下,致力于降低使用大模型训练的门槛与成本,推动人工智能更加普惠。而潞晨科技作为重要合作伙伴,将继续发挥关键作用。
-
自然语言处理里的文本纠错实操:以基于规则的算法为例 - 第4章详细解析
-
自然语言处理实用入门 ---- 第 4 课:中文解析原理及相关组件介绍--《自然语言处理》中的主要解析算法、组件和服务 ...
-
探索自然语言处理中的元理论算法
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。
-
F#探险之旅(二):函数式编程(上)-函数式编程范式简介 F#主要支持三种编程范式:函数式编程(Functional Programming,FP)、命令式编程(Imperative Programming)和面向对象(Object-Oriented,OO)的编程。回顾它们的历史,FP是最早的一种范式,第一种FP语言是IPL,产生于1955年,大约在Fortran一年之前。第二种FP语言是Lisp,产生于1958,早于Cobol一年。Fortan和Cobol都是命令式编程语言,它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟,至今已是最流行的编程范式。有道是“*代有语言出,各领风骚数十年”。 尽管强大的FP语言(SML,Ocaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两个)在1950年代就不断发展,FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据领先。今天,FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。 纯粹的FP将程序看作是接受参数并返回值的函数的集合,它不允许有副作用(side effect,即改变了状态),使用递归而不是循环进行迭代。FP中的函数很像数学中的函数,它们都不改变程序的状态。举个简单的例子,一旦将一个值赋给一个标识符,它就不会改变了,函数不改变参数的值,返回值是全新的值。 FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮。但它无状态和递归的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题,它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好的范式。 关于FP的更多内容建议阅读一下这篇文章:Why Functional Programming Matters(中文版)。F#中的函数式编程 从现在开始,我将对F#中FP相关的主要语言结构逐一进行介绍。标识符(Identifier) 在F#中,我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它。通过关键字let定义标识符,如: let x = 42 这看起来像命令式编程语言中的赋值语句,两者有着关键的不同。在纯粹的FP中,一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因。另外,在某些条件下,我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的。 标识符也可用于引用函数,在F#中函数本质上也是值。也就是说,F#中没有真正的函数名和参数名的概念,它们都是标识符。定义函数的方式与定义值是类似的,只是会有额外的标识符表示参数: let add x y = x + y 这里共有三个标识符,add表示函数名,x和y表示它的参数。关键字和保留字关键字是指语言中一些标记,它们被编译器保留作特殊之用。在F#中,不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是: abstract and as asr assert begin class default delegate do donedowncast downto elif else end exception extern false finally forfun function if in inherit inline interface internal land lazy letlor lsr lxor match member mod module mutable namespace new nullof open or override private public rec return sig static structthen to true try type upcast use val void when while with yield 保留字是指当前还不是关键字,但被F#保留做将来之用。可以用它们来定义标识符或类型名称,但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性,最好不要使用。它们是: atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixinobject parallel process protected pure sealed trait virtual volatile 文字值(Literals) 文字值表示常数值,在构建计算代码块时很有用,F#提供了丰富的文字值集。与C#类似,这些文字值包括了常见的字符串、字符、布尔值、整型数、浮点数等,在此不再赘述,详细信息请查看F#手册。 与C#一样,F#中的字符串常量表示也有两种方式。一是常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string),其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示。下面这个简单的例子演示了常见的文字常量表示: let message = "Hello World"r"n!" // 常规字符串let dir = @"C:"FS"FP" // 逐字字符串let bytes = "bytes"B // byte 数组let xA = 0xFFy // sbyte, 16进制表示let xB = 0o777un // unsigned native-sized integer,8进制表示let print x = printfn "%A" xlet main = print message; print dir; print bytes; print xA; print xB; main Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值,也可以通过F#中的print_any和print_to_string函数来完成类似的功能。值和函数(Values and Functions) 在F#中函数也是值,F#处理它们的语法也是类似的。 let n = 10let add a b = a + blet addFour = add 4let result = addFour n printfn "result = %i" result 可以看到定义值n和函数add的语法很类似,只不过add还有两个参数。对于add来说a + b的值自动作为其返回值,也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说,它定义在add的基础上,它只向add传递了一个参数,这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念,F(x, y) = x + y,G(y) = F(4, y),实际上G(y) = 4 + y,G也是一个函数,它接收一个参数,这个地方是不是很类似?这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。 当然对某些函数来说,传递部分参数是无意义的,此时需要强制提供所有参数,可是将参数括起来,将它们转换为元组(tuple)。下面的例子将不能编译通过: let sub(a, b) = a - blet subFour = sub 4 必须为sub提供两个参数,如sub(4, 5),这样就很像C#中的方法调用了。 对于这两种方式来说,前者具有更高的灵活性,一般可优先考虑。 如果函数的计算过程中需要定义一些中间值,我们应当将这些行进行缩进: let halfWay a b = let dif = b - a let mid = dif / 2 mid + a 需要注意的是,缩进时要用空格而不是Tab,如果你不想每次都按几次空格键,可以在VS中设置,将Tab字符自动转换为空格;虽然缩进的字符数没有限制,但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令。作用域(Scope)作用域是编程语言中的一个重要的概念,它表示在何处可以访问(使用)一个标识符或类型。所有标识符,不管是函数还是值,其作用域都从其声明处开始,结束自其所处的代码块。对于一个处于最顶层的标识符而言,一旦为其赋值,它的值就不能修改或重定义了。标识符在定义之后才能使用,这意味着在定义过程中不能使用自身的值。 let defineMessage = let message = "Help me" print_endline message // error 对于在函数内部定义的标识符,一般而言,它们的作用域会到函数的结束处。 但可使用let关键字重定义它们,有时这会很有用,对于某些函数来说,计算过程涉及多个中间值,因为值是不可修改的,所以我们就需要定义多个标识符,这就要求我们去维护这些标识符的名称,其实是没必要的,这时可以使用重定义标识符。但这并不同于可以修改标识符的值。你甚至可以修改标识符的类型,但F#仍能确保类型安全。所谓类型安全,其基本意义是F#会避免对值的错误操作,比如我们不能像对待字符串那样对待整数。这个跟C#也是类似的。 let changeType = let x = 1 let x = "change me" let x = x + 1 print_string x 在本例的函数中,第一行和第二行都没问题,第三行就有问题了,在重定义x的时候,赋给它的值是x + 1,而x是字符串,与1相加在F#中是非法的。 另外,如果在嵌套函数中重定义标识符就更有趣了。 let printMessages = let message = "fun value" printfn "%s" message; let innerFun = let message = "inner fun value" printfn "%s" message innerFun printfn "%s" message printMessages 打印结果: fun value inner fun valuefun value 最后一次不是inner fun value,因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部。递归(Recursion)递归是编程中的一个极为重要的概念,它表示函数通过自身进行定义,亦即在定义处调用自身。在FP中常用于表达命令式编程的循环。很多人认为使用递归表示的算法要比循环更易理解。 使用rec关键字进行递归函数的定义。看下面的计算阶乘的函数: let rec factorial x = match x with | x when x < 0 -> failwith "value must be greater than or equal to 0" | 0 -> 1 | x -> x * factorial(x - 1) 这里使用了模式匹配(F#的一个很棒的特性),其C#版本为: public static long Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("value must be greater than or equal to 0"); } if (n == 0) { return 1; } return n * Factorial (n - 1); } 递归在解决阶乘、Fibonacci数列这样的问题时尤为适合。但使用的时候要当心,可能会写出不能终止的递归。匿名函数(Anonymous Function) 定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名,这种函数就是所谓的匿名函数,有时称为lambda函数,这也是C#3.0的一个新特性。比如有的函数仅仅作为一个参数传给另一个函数,通常就不需要起名。在后面的“列表”一节中你会看到这样的例子。除了fun,我们还可以使用function关键字定义匿名函数,它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子: let x = (fun x y -> x + y) 1 2let x1 = (function x -> function y -> x + y) 1 2let x2 = (function (x, y) -> x + y) (1, 2) 我们可优先考虑fun,因为它更为紧凑,在F#类库中你能看到很多这样的例子。 注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。 F#系列随笔索引页面