基于前馈神经网络的姓氏分类任务(基础)

1、认识前馈神经网络

What is it

image.png

图1-1 前馈神经网络结构

人们大多使用多层感知机(英语:Multilayer Perceptron,缩写:MLP)作为前馈神经网络的代名词,但是除了MLP之外,卷积神经网络(英语:convolutional neural network,缩写:CNN)也是典型的前馈神经网络。

Why need it

感知机

先来简单了解一下感知机(perceptron),感知机是一种线性分类算法,通常用于二分类问题。它非常松散地模仿生物神经元,就像一条简单的神经元那样,有输入(刺激)和输出,并且用激活函数来模仿放电阈值,“信号”从输入流向输出,如图1-2所示

图1-2 感知机模型

数学上,感知机就是一个简单的线性函数:

y=f(wx+b)y=f(wx+b)y=f(wx+b)

日常问题中,通常有多个输入,用向量表示这个一般情况;即:x和w是向量,w和x的乘积替换为点积:

y=f(w⃗Tx⃗+b)y=f(\vec{w}^T\vec{x}+b)y=f(wTx+b)

激活函数用f表示,通常是一个非线性函数。

图 1-3 感知机工作原理

创建一个线性可分的数据集。数据集中的两个类绘制为圆形和星形。感知机要做的就是区分数据集上的点是圆形还是星型。感知机可以很好的处理线性可分的问题,例如经典的与(AND)或(OR)非(NOT)问题。

但是碰上非线性可分问题,例如异或(XOR)问题,感知机就束手无策了,由于没办法用线性函数分割数据,感知机模型无法收敛会导致其一直迭代。由于单层感知机不能处理异或问题,神经网络甚至一度陷入低潮。

多层感知机(MLP)

MLP是对感知机的扩展,感知机将数据向量作为输入,计算出一个输出值。在MLP中,许多感知机被分组成为各个网络层,每一层的输出是一个新的向量,而不是单个输出值。也就是说MLP把多个感知机的输出结果作为,最简单的MLP,如图4-2所示,由三个表示阶段和两个线性层组成。

image.png 一种具有两个线性层和三个表示阶段(输入向量、隐藏向量和输出向量)的MLP模型,如下图1-4所示:

图1-4 MLP可视化表示

图1-5 XOR(异或)数据集中的两个类绘制为圆形和星形。

在这个分类问题中,不存在任何一条直线可以分隔这两个类,这也就是线性不可分问题。 普通的感知机面对这种问题就捉襟见肘了,但是对多层感知机来说,这不是个事。

图1-6 从感知器(左)和MLP(右)学习的XOR问题的解决方案显示

虽然在图中显示MLP好像两个决策边界,但它实际上只是一个决策边界!决策边界就是这样出现的,因为MLP中间通过某种神奇的表示法改变了空间,使一个超平面同时出现在这两个位置上。在图1-7中,我们可以看到MLP计算的中间过程。这些点的形状表示类(星形或圆形)。我们所看到的是,MLP已经学会了“扭曲”数据所处的空间,以便在数据通过最后一层时,用一条直线来分割它们。

图1-7 MLP的输入和中间表示是可视化的。从左到右:(1)网络的输入;(2)第一个线性模块的输出;(3)第一个非线性模块的输出;(4)第二个线性模块的输出。第一个线性模块的输出将圆和星分组,而第二个线性模块的输出将数据点重新组织为线性可分的。

与之相反,如图1-8所示,感知机没有额外的一层来处理数据的形状,直到数据变成线性可分的。

图1-8 感知器的输入和输出表示。因为它没有像MLP那样的中间表示来分组和重新组织,所以它不能将圆和星分开。

How do achieve it

好了,既然我们已经知道MLP神通广大了,肯定迫不及待的想要实现它了。

2、在PyTorch中实现MLP

MLP除了由许多简单的感知器构成之外,还有一个额外的计算层。我们可以用PyTorch的两个线性模块表示。将线性模块称为“完全连接层”,简称为“fc层”。除了这两个线性层外,还有一个修正的非线性单元(ReLU),它的输入是第一个线性层的输出,它的输出作为第二个线性层的输入。在两个线性层之间加入非线性单元是必要的,因为没有它,两个线性层在数学上等价于一个线性层。基于pytorch的MLP只实现反向传播的前向传递。因为PyTorch根据模型的定义和向前传递的实现,自动计算出如何进行向后传递和梯度更新。

实例化MLP

import torch.nn as nn  # 导入PyTorch中神经网络模块
import torch.nn.functional as F  # 导入PyTorch中函数式接口,用于激活函数等操作

class MultilayerPerceptron(nn.Module):  # 定义一个多层感知机类
    def __init__(self, input_dim, hidden_dim, output_dim):
        '''
        初始化函数,设置MLP的层和参数。
        Args:
            input_dim (int): 输入向量的维度
            hidden_dim (int): 第一个线性层的输出维度,即隐藏层的大小
            output_dim (int): 第二个线性层的输出维度,即输出层的大小
        '''
        super(MultilayerPerceptron, self).__init__()  # 调用基类的初始化方法
        self.fc1 = nn.Linear(input_dim, hidden_dim)  # 第一个线性层,从输入层到隐藏层
        self.fc2 = nn.Linear(hidden_dim, output_dim)  # 第二个线性层,从隐藏层到输出层

    def forward(self, x_in, apply_softmax=False):
        """
        前向传播函数,定义了数据如何通过网络流动。
        Args:
            x_in (torch.Tensor): 输入数据张量,其形状应为(batch, input_dim)
            apply_softmax (bool): 是否应用softmax激活函数
                如果与交叉熵损失一起使用,应设置为False
        Returns:
            torch.Tensor: 结果张量,其形状应为(batch, output_dim)
        """
        intermediate = F.relu(self.fc1(x_in))  # 应用第一个线性层并使用ReLU激活函数
        output = self.fc2(intermediate)  # 应用第二个线性层

        if apply_softmax:  # 如果需要,应用softmax激活函数
            output = F.softmax(output, dim=1)  # softmax沿最后一个维度计算
        return output  # 返回最终的输出张量

由于MLP实现的通用性,可以为任何大小的输入建模。为了演示,我们使用大小为3的输入维度、大小为4的输出维度和大小为100的隐藏维度。

batch_size = 2  # 定义一次输入的样本数量
input_dim = 3   # 定义输入向量的维度,例如,每个样本有3个特征
hidden_dim = 100  # 定义隐藏层的神经元数量,这里是100个神经元
output_dim = 4  # 定义输出层的神经元数量,例如,对于4类分类问题

# 初始化模型
mlp = MultilayerPerceptron(input_dim, hidden_dim, output_dim)
# 使用定义好的类构造函数创建一个多层感知机实例

print(mlp)  # 打印模型的结构
# 这将输出模型的层和参数信息,包括每个层的类型和参数数量

上述代码运行结果:

MultilayerPerceptron(
  (fc1): Linear(in_features=3, out_features=100, bias=True)
  (fc2): Linear(in_features=100, out_features=4, bias=True)
)

测试模型的连接

我们可以通过传递一些随机输入来快速测试模型的“连接”,但是因为模型还没有经过训练,所以输出是随机的。在花费时间训练模型之前,这样做是一个有用的完整性检查。

def describe(x):
    """
    打印张量的类型、形状和值。
    Args:
        x (torch.Tensor): 需要描述的张量
    """
    print("Type: {}".format(x.type()))  # 打印张量的类型,例如torch.float32
    print("Shape/size: {}".format(x.shape))  # 打印张量的形状,例如torch.Size([2, 3])
    print("Values: \n{}".format(x))  # 打印张量的值

x_input = torch.rand(batch_size, input_dim)  # 创建一个形状为(batch_size, input_dim)的随机张量
# torch.rand生成[0,1)区间内均匀分布的随机数

describe(x_input)  # 使用describe函数打印x_input张量的信息

上述代码运行结果:

Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values: 
tensor([[0.6193, 0.7045, 0.7812],
        [0.6345, 0.4476, 0.9909]])

y_output = mlp(x_input, apply_softmax=False)  # 使用多层感知机模型对x_input进行前向传播
# 这里apply_softmax参数设置为False,表示不应用softmax激活函数,通常在模型训练时这样做,
# 因为PyTorch的交叉熵损失函数会内部应用softmax。

describe(y_output)  # 使用describe函数打印y_output张量的信息

上述代码运行结果:

Type: torch.FloatTensor
Shape/size: torch.Size([2, 4])
Values: 
tensor([[ 0.2356,  0.0983, -0.0111, -0.0156],
        [ 0.1604,  0.1586, -0.0642,  0.0010]], grad_fn=<AddmmBackward>)

读取模型的输入和输出

在前面的例子中,MLP模型的输出是一个有两行四列的张量。在某些情况下,例如在分类设置中,特征向量是一个预测向量。名称为“预测向量”表示它对应于一个概率分布。预测向量会发生什么取决于我们当前是在进行训练还是在执行推理。在训练期间,输出按原样使用,带有一个损失函数和目标类标签的表示。

但是,如果想将预测向量转换为概率,则需要额外的步骤。具体来说,需要softmax函数,它可以将输出值向量转换为概率。这个函数背后的直觉是,大的正值会导致更高的概率,小的负值会导致更小的概率。下面将apply_softmax标志设置为True:

y_output = mlp(x_input, apply_softmax=True)  # 使用多层感知机模型对x_input进行前向传播
# 这里apply_softmax参数设置为True,表示在模型的输出上应用softmax激活函数,
# 这通常用于模型的预测阶段,将原始分数转换为概率分布。

describe(y_output)  # 使用describe函数打印y_output张量的信息

上述代码运行结果:

Type: torch.FloatTensor
Shape/size: torch.Size([2, 4])
Values: 
tensor([[0.2915, 0.2541, 0.2277, 0.2267],
        [0.2740, 0.2735, 0.2189, 0.2336]], grad_fn=<SoftmaxBackward>)

综上所述,mlp是将输入张量映射到输出张量的线性层。并且在每一对线性层之间使用非线性来打破线性关系,允许模型扭曲向量空间。在分类设置中,这种扭曲导致类之间的线性可分性。另外,可以使用softmax函数将MLP输出解释为概率。

3、MLP在姓氏分类中的应用

在本节中,我们将MLP应用于将姓氏分类到其原籍国的任务。我们首先对每个姓氏的字符进行拆分。除了数据上的差异,字符层模型在结构和实现上与基于单词的模型基本相似。我们在这个例子中引入了多类输出及其对应的损失函数。在建立了模型之后,我们完成了训练。 要先从从姓氏数据集及其预处理步骤的描述开始。然后,我们使用词汇表、向量化器和DataLoader类逐步完成从姓氏字符串到向量化小批处理的管道。

建立词汇表

    #Vocabulary
from argparse import Namespace
from collections import Counter
import json
import os
import string

import numpy as np
import pandas as pd

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm_notebook
class Vocabulary(object):
    """用于处理文本并提取词汇表进行映射的类"""

    def __init__(self, token_to_idx=None, add_unk=True, unk_token="<UNK>"):
        """
        初始化 Vocabulary 类的一个实例。

        Args:
            token_to_idx (dict): 一个预先存在的标记到索引的映射字典。
            add_unk (bool): 一个标志,指示是否添加未知标记(UNK token)。
            unk_token (str): 要添加到词汇表中的未知标记。
        """
        # 如果没有提供 token_to_idx,则创建一个空字典
        if token_to_idx is None:
            token_to_idx = {}
        self._token_to_idx = token_to_idx

        # 创建索引到标记的反向映射
        self._idx_to_token = {idx: token for token, idx in self._token_to_idx.items()}

        # 设置是否添加未知标记和未知标记的值
        self._add_unk = add_unk
        self._unk_token = unk_token

        # 设置未知标记的索引,如果需要
        self.unk_index = -1
        if add_unk:
            self.unk_index = self.add_token(unk_token)

    def to_serializable(self):
        """返回一个可序列化的字典"""
        # 返回包含词汇表状态的字典,以便可以序列化
        return {
            'token_to_idx': self._token_to_idx,
            'add_unk': self._add_unk,
            'unk_token': self._unk_token
        }

    @classmethod
    def from_serializable(cls, contents):
        """从序列化的字典实例化 Vocabulary """
        # 根据序列化的内容(字典)创建 Vocabulary 类的实例
        return cls(**contents)

    def add_token(self, token):
        """根据标记更新映射字典。

        Args:
            token (str): 要添加到词汇表中的项

        Returns:
            index (int): 与标记对应的整数
        """
        # 尝试从现有映射中获取标记的索引,如果不存在则添加标记并创建新索引
        try:
            index = self._token_to_idx[token]
        except KeyError:
            index = len(self._token_to_idx)
            self._token_to_idx[token] = index
            self._idx_to_token[index] = token
        return index

    def add_many(self, tokens):
        """将一系列标记添加到词汇表中

        Args:
            tokens (list): 字符串标记的列表

        Returns:
            indices (list): 与标记对应的索引列表
        """
        # 对列表中的每个标记调用 add_token,并返回索引列表
        return [self.add_token(token) for token in tokens]

    def lookup_token(self, token):
        """检索与标记关联的索引,如果标记不存在则返回未知标记的索引。

        Args:
            token (str): 要查找的标记

        Returns:
            index (int): 与标记对应的索引
        """
        # 如果已添加未知标记,则返回标记的索引或未知标记的索引
        if self.unk_index >= 0:
            return self._token_to_idx.get(token, self.unk_index)
        else:
            return self._token_to_idx[token]

    def lookup_index(self, index):
        """返回与索引关联的标记

        Args:
            index (int): 要查找的索引

        Returns:
            token (str): 与索引对应的标记

        Raises:
            KeyError: 如果索引不在词汇表中
        """
        # 如果索引在索引到标记的映射中,则返回对应的标记,否则抛出 KeyError
        if index not in self._idx_to_token:
            raise KeyError("the index (%d) is not in the Vocabulary" % index)
        return self._idx_to_token[index]

    def __str__(self):
        """返回 Vocabulary 对象的字符串表示形式"""
        return "<Vocabulary(size=%d)>" % len(self)

    def __len__(self):
        """返回词汇表的大小"""
        return len(self._token_to_idx)

定义姓氏数据集类

姓氏数据集,它收集了来自18个不同国家的10,000个姓氏,这些姓氏是作者从互联网上不同的姓名来源收集的。该数据集将在本课程实验的几个示例中重用,并具有一些使其有趣的属性。第一个性质是它是相当不平衡的。排名前三的课程占数据的60%以上:27%是英语,21%是俄语,14%是阿拉伯语。剩下的15个民族的频率也在下降——这也是语言特有的特性。第二个特点是,在国籍和姓氏正字法(拼写)之间有一种有效和直观的关系。有些拼写变体与原籍国联系非常紧密(比如“O ‘Neill”、“Antonopoulos”、“Nagasawa”或“Zhu”)。

为了创建最终的数据集,要进行几个数据集修改操作。第一个目的是减少这种不平衡——原始数据集中70%以上是俄文,这可能是由于抽样偏差或俄文姓氏的增多。为此,我们通过选择标记为俄语的姓氏的随机子集对这个过度代表的类进行子样本。接下来,我们根据国籍对数据集进行分组,并将数据集分为三个部分:70%到训练数据集,15%到验证数据集,最后15%到测试数据集,以便跨这些部分的类标签分布具有可比性。

from torch.utils.data import Dataset  # 导入PyTorch的Dataset类
import pandas as pd
class SurnameDataset(Dataset):
    """
    自定义数据集类,用于处理姓氏和国籍的数据。
    实现与第3.5节几乎相同(假设这是参考某个教程或文档中的一个部分)。
    """
    def __init__(self, surname_df, vectorizer):
        """
        Args:
            surname_df (pandas.DataFrame): the dataset
            vectorizer (SurnameVectorizer): vectorizer instatiated from dataset
        """
        self.surname_df = surname_df
        self._vectorizer = vectorizer

        self.train_df = self.surname_df[self.surname_df.split=='train']
        self.train_size = len(self.train_df)

        self.val_df = self.surname_df[self.surname_df.split=='val']
        self.validation_size = len(self.val_df)

        self.test_df = self.surname_df[self.surname_df.split=='test']
        self.test_size = len(self.test_df)

        self._lookup_dict = {'train': (self.train_df, self.train_size),
                             'val': (self.val_df, self.validation_size),
                             'test': (self.test_df, self.test_size)}

        self.set_split('train')
        
        # Class weights
        class_counts = surname_df.nationality.value_counts().to_dict()
        def sort_key(item):
            return self._vectorizer.nationality_vocab.lookup_token(item[0])
        sorted_counts = sorted(class_counts.items(), key=sort_key)
        frequencies = [count for _, count in sorted_counts]
        self.class_weights = 1.0 / torch.tensor(frequencies, dtype=torch.float32)

    @classmethod
    def load_dataset_and_make_vectorizer(cls, surname_csv):
        """Load dataset and make a new vectorizer from scratch
        
        Args:
            surname_csv (str): location of the dataset
        Returns:
            an instance of SurnameDataset
        """
        surname_df = pd.read_csv(surname_csv)
        train_surname_df = surname_df[surname_df.split=='train']
        return cls(surname_df, SurnameVectorizer.from_dataframe(train_surname_df))

    @classmethod
    def load_dataset_and_load_vectorizer(cls, surname_csv, vectorizer_filepath):
        """Load dataset and the corresponding vectorizer. 
        Used in the case in the vectorizer has been cached for re-use
        
        Args:
            surname_csv (str): location of the dataset
            vectorizer_filepath (str): location of the saved vectorizer
        Returns:
            an instance of SurnameDataset
        """
        surname_df = pd.read_csv(surname_csv)
        vectorizer = cls.load_vectorizer_only(vectorizer_filepath)
        return cls(surname_df, vectorizer)

    @staticmethod
    def load_vectorizer_only(vectorizer_filepath):
        """a static method for loading the vectorizer from file
        
        Args:
            vectorizer_filepath (str): the location of the serialized vectorizer
        Returns:
            an instance of SurnameVectorizer
        """
        with open(vectorizer_filepath) as fp:
            return SurnameVectorizer.from_serializable(json.load(fp))

    def save_vectorizer(self, vectorizer_filepath):
        """saves the vectorizer to disk using json
        
        Args:
            vectorizer_filepath (str): the location to save the vectorizer
        """
        with open(vectorizer_filepath, "w") as fp:
            json.dump(self._vectorizer.to_serializable(), fp)

    def get_vectorizer(self):
        """ returns the vectorizer """
        return self._vectorizer

    def set_split(self, split="train"):
        """ selects the splits in the dataset using a column in the dataframe """
        self._target_split = split
        self._target_df, self._target_size = self._lookup_dict[split]

    def __len__(self):
        return self._target_size
    
    def __getitem__(self, index):
        """
        根据索引index获取数据集中的一个样本。
        
        Args:
            index (int): 要获取的样本的索引
        
        Returns:
            dict: 包含一个样本的特征和标签的字典
        """
        row = self._target_df.iloc[index]  # 从数据集中获取第index行的数据
        # 这里假设self._target_df是一个pandas DataFrame对象,存储了数据集的所有行

        surname_vector = self._vectorizer.vectorize(row.surname)  # 使用向量化方法将姓氏转换为数值向量
        # 这里假设self._vectorizer是一个具有vectorize方法的对象,用于将文本数据转换为数值向量

        nationality_index = self._vectorizer.nationality_vocab.lookup_token(row.nationality)  # 将国籍转换为索引
        # 这里假设self._vectorizer.nationality_vocab是一个Vocabulary对象,用于将文本标签转换为整数索引

        return {'x_surname': surname_vector,  # 返回一个字典,包含特征和标签
                'y_nationality': nationality_index}
    def get_num_batches(self, batch_size):
        """Given a batch size, return the number of batches in the dataset
        
        Args:
            batch_size (int)
        Returns:
            number of batches in the dataset
        """
        return len(self) // batch_size

    
def generate_batches(dataset, batch_size, shuffle=True,
                     drop_last=True, device="cpu"): 
    """
    A generator function which wraps the PyTorch DataLoader. It will 
      ensure each tensor is on the write device location.
    """
    dataloader = DataLoader(dataset=dataset, batch_size=batch_size,
                            shuffle=shuffle, drop_last=drop_last)

    for data_dict in dataloader:
        out_data_dict = {}
        for name, tensor in data_dict.items():
            out_data_dict[name] = data_dict[name].to(device)
        yield out_data_dict

构建词汇表向量化的向量化器

虽然词汇表将单个令牌(字符)转换为整数,但SurnameVectorizer负责应用词汇表并将姓氏转换为向量。字符串没有在空格上分割。姓氏是字符的序列,每个字符在我们的词汇表中是一个单独的标记。在“卷积神经网络”出现之前,我们将忽略序列信息,通过迭代字符串输入中的每个字符来创建输入的收缩one-hot向量表示。

class SurnameVectorizer(object):
    """协调姓氏和国籍词汇表,并将其用于向量化处理的向量化器"""

    def __init__(self, surname_vocab, nationality_vocab):
        """
        初始化向量化器,设置用于姓氏和国籍的词汇表。

        Args:
            surname_vocab (Vocabulary): 将字符映射到整数的词汇表
            nationality_vocab (Vocabulary): 将国籍映射到整数的词汇表
        """
        self.surname_vocab = surname_vocab  # 存储姓氏字符的词汇表
        self.nationality_vocab = nationality_vocab  # 存储国籍的词汇表

    def vectorize(self, surname):
        """
        将提供的姓氏进行向量化处理,生成独热编码。

        Args:
            surname (str): 姓氏字符串

        Returns:
            one_hot (np.ndarray): 折叠的独热编码数组
        """
        vocab = self.surname_vocab  # 获取姓氏的词汇表
        one_hot = np.zeros(len(vocab), dtype=np.float32)  # 创建一个零向量,长度为词汇表大小
        for token in surname:  # 遍历姓氏中的每个字符
            one_hot[vocab.lookup_token(token)] = 1  # 在独热编码向量中对应位置设为1

        return one_hot  # 返回独热编码数组

    @classmethod
    def from_dataframe(cls, surname_df):
        """从数据集的DataFrame创建向量化器实例
        
        Args:
            surname_df (pandas.DataFrame): 包含姓氏数据的数据集
        Returns:
            SurnameVectorizer的一个实例
        """
        surname_vocab = Vocabulary(unk_token="@")  # 创建用于姓氏的词汇表,使用"@"作为未知标记
        nationality_vocab = Vocabulary(add_unk=False)  # 创建用于国籍的词汇表,不添加未知标记

        for index, row in surname_df.iterrows():  # 遍历数据集中的每一行
            for letter in row.surname:  # 对于每个姓氏中的每个字符
                surname_vocab.add_token(letter)  # 将字符添加到姓氏词汇表中
            nationality_vocab.add_token(row.nationality)  # 将国籍添加到国籍词汇表中

        return cls(surname_vocab, nationality_vocab)  # 使用创建的词汇表实例化向量化器并返回

    @classmethod
    def from_serializable(cls, contents):
        """从序列化的字典创建向量化器实例
        
        Args:
            contents (dict): 包含序列化信息的字典
        Returns:
            SurnameVectorizer的一个实例
        """
        surname_vocab = Vocabulary.from_serializable(contents['surname_vocab'])  # 从序列化数据创建姓氏词汇表
        nationality_vocab = Vocabulary.from_serializable(contents['nationality_vocab'])  # 从序列化数据创建国籍词汇表
        return cls(surname_vocab=surname_vocab, nationality_vocab=nationality_vocab)  # 使用创建的词汇表实例化向量化器

    def to_serializable(self):
        """返回向量化器的序列化形式"""
        return {
            'surname_vocab': self.surname_vocab.to_serializable(),  # 获取姓氏词汇表的序列化形式
            'nationality_vocab': self.nationality_vocab.to_serializable()  # 获取国籍词汇表的序列化形式
        }

构造姓氏分类的MLP模型

SurnameClassifier是前面介绍的MLP的实现。第一个线性层将输入向量映射到中间向量,并对该向量应用非线性。第二线性层将中间向量映射到预测向量。

在最后一步中,可选地应用softmax操作,以确保输出和为1;这就是所谓的“概率”。它是可选的原因与我们使用的损失函数的数学公式有关——交叉熵损失。我们研究了“损失函数”中的交叉熵损失。回想一下,交叉熵损失对于多类分类是最理想的,但是在训练过程中软最大值的计算不仅浪费而且在很多情况下并不稳定。

class SurnameClassifier(nn.Module):
    """
    一个用于分类姓氏的两层多层感知机(MLP)。
    """

    def __init__(self, input_dim, hidden_dim, output_dim):
        """
        初始化分类器,设置网络层和参数。

        Args:
            input_dim (int): 输入向量的维度大小。
            hidden_dim (int): 第一个线性层的输出维度大小,即隐藏层的大小。
            output_dim (int): 第二个线性层的输出维度大小,即输出层的大小,通常与类别数相同。
        """
        super(SurnameClassifier, self).__init__()  # 调用基类的初始化方法
        self.fc1 = nn.Linear(input_dim, hidden_dim)  # 第一个线性层,从输入层到隐藏层
        self.fc2 = nn.Linear(hidden_dim, output_dim)  # 第二个线性层,从隐藏层到输出层

    def forward(self, x_in, apply_softmax=False):
        """
        前向传播函数,定义了数据通过网络的流动方式。

        Args:
            x_in (torch.Tensor): 输入数据张量,其形状应为 (batch, input_dim)。
            apply_softmax (bool): 是否应用 softmax 激活函数的标志。
                如果与交叉熵损失函数一起使用,应设置为 False。

        Returns:
            torch.Tensor: 结果张量,其形状应为 (batch, output_dim)。
        """
        intermediate_vector = F.relu(self.fc1(x_in))  # 应用第一个线性层并使用 ReLU 激活函数
        prediction_vector = self.fc2(intermediate_vector)  # 应用第二个线性层

        if apply_softmax:  # 如果需要,应用 softmax 激活函数
            prediction_vector = F.softmax(prediction_vector, dim=1)  # 对最后一个维度应用 softmax

        return prediction_vector  # 返回预测结果张量

对模型进行训练吧

训练中最显著的差异与模型中输出的种类和使用的损失函数有关。在这个例子中,输出是一个多类预测向量,可以转换为概率。正如在模型描述中所描述的,这种输出的损失类型仅限于CrossEntropyLoss和NLLLoss。由于它的简化,我们使用了CrossEntropyLoss。

import numpy as np
def set_seed_everywhere(seed, cuda):
    """
    设置随机种子,确保结果可重复。

    Args:
        seed (int): 随机种子值。
        cuda (bool): 是否使用 CUDA(GPU)。
    """
    np.random.seed(seed)  # 设置 NumPy 的随机种子
    torch.manual_seed(seed)  # 设置 PyTorch 的随机种子
    if cuda:
        torch.cuda.manual_seed_all(seed)  # 如果使用 CUDA,设置所有 GPU 的随机种子

def handle_dirs(dirpath):
    """
    处理目录,如果不存在则创建。

    Args:
        dirpath (str): 目录的路径。
    """
    if not os.path.exists(dirpath):  # 检查目录是否存在
        os.makedirs(dirpath)  # 如果目录不存在,则创建目录
from argparse import Namespace
import os
import torch

# 创建一个Namespace对象,用于存储命令行参数解析的结果
args = Namespace(
    # 数据和路径信息
    surname_csv="data/surnames/surnames_with_splits.csv",  # 姓氏数据CSV文件的路径
    vectorizer_file="vectorizer.json",  # 向量化器文件的名称
    model_state_file="model.pth",  # 模型状态文件的名称
    save_dir="model_storage/ch4/surname_mlp",  # 模型和向量化器文件的保存目录

    # 模型超参数
    hidden_dim=300,  # 隐藏层的维度

    # 训练超参数
    seed=1337,  # 随机种子,用于确保实验的可重复性
    num_epochs=100,  # 训练的最大轮数
    early_stopping_criteria=5,  # 早期停止的标准
    learning_rate=0.001,  # 学习率
    batch_size=64,  # 每个批次的样本数量

    # 运行时选项
    cuda=False,  # 是否使用CUDA(GPU)进行训练
    reload_from_files=False,  # 是否从文件中重新加载数据
    expand_filepaths_to_save_dir=True,  # 是否将文件路径扩展到保存目录
)

# 如果设置了将文件路径扩展到保存目录
if args.expand_filepaths_to_save_dir:
    # 将向量化器文件和模型状态文件的路径与保存目录合并
    args.vectorizer_file = os.path.join(args.save_dir, args.vectorizer_file)
    args.model_state_file = os.path.join(args.save_dir, args.model_state_file)
    
    print("Expanded filepaths: ")
    print("\t{}".format(args.vectorizer_file))  # 打印扩展后的向量化器文件路径
    print("\t{}".format(args.model_state_file))  # 打印扩展后的模型状态文件路径

# 检查CUDA是否可用
if not torch.cuda.is_available():  # 如果CUDA不可用
    args.cuda = False  # 设置args中的cuda选项为False

# 设置设备,根据是否使用CUDA决定使用CPU或GPU
args.device = torch.device("cuda" if args.cuda else "cpu")

# 打印是否使用CUDA的信息
print("Using CUDA: {}".format(args.cuda))

# 为了可重复性,设置随机种子
set_seed_everywhere(args.seed, args.cuda)

# 处理目录,确保保存目录存在
handle_dirs(args.save_dir)

上述代码运行结果(不同设备的结果不一致):

Expanded filepaths: 
	model_storage/ch4/surname_mlp/vectorizer.json
	model_storage/ch4/surname_mlp/model.pth
Using CUDA: False

# 使用 SurnameDataset 类的类方法 load_dataset_and_make_vectorizer 加载数据集
# 并创建一个向量化器,传入 args 中指定的 CSV 文件路径
dataset = SurnameDataset.load_dataset_and_make_vectorizer(args.surname_csv)

# 从 dataset 对象获取向量化器
vectorizer = dataset.get_vectorizer()

# 初始化 SurnameClassifier 类的实例,即我们的模型
# input_dim 设置为向量化后的姓氏特征的维度,即姓氏词汇表的大小
# hidden_dim 设置为 args 中定义的隐藏层维度
# output_dim 设置为向量化后的国籍特征的维度,即国籍词汇表的大小
classifier = SurnameClassifier(input_dim=len(vectorizer.surname_vocab),
                               hidden_dim=args.hidden_dim,
                               output_dim=len(vectorizer.nationality_vocab))

# 将模型 classifier 移动到 args 中指定的设备上,这通常是一个 GPU 或 CPU
classifier = classifier.to(args.device)

# 初始化损失函数,这里使用 PyTorch 的 CrossEntropyLoss
# dataset.class_weights 可能是一个由数据集类计算的权重,用于处理类别不平衡问题
loss_func = nn.CrossEntropyLoss(dataset.class_weights)

# 初始化优化器,这里使用 PyTorch 的 Adam 算法
# 传入模型的参数 classifier.parameters() 和 args 中定义的学习率
optimizer = optim.Adam(classifier.parameters(), lr=args.learning_rate)

    # 如果设置了从文件重新加载
if args.reload_from_files:
    # 打印信息,表明正在重新加载
    print("Reloading!")
    
    # 从指定的CSV和向量化器文件中加载数据集和向量化器
    dataset = SurnameDataset.load_dataset_and_load_vectorizer(
        args.surname_csv,  # 指定的CSV文件路径
        args.vectorizer_file  # 指定的向量化器文件路径
    )
else:
    # 如果没有设置从文件重新加载
    # 打印信息,表明正在从头开始创建
    print("Creating fresh!")
    
    # 从指定的CSV文件中加载数据集并创建新的向量化器
    dataset = SurnameDataset.load_dataset_and_make_vectorizer(args.surname_csv)
    
    # 保存新创建的向量化器到文件
    dataset.save_vectorizer(args.vectorizer_file)

# 获取数据集的向量化器
vectorizer = dataset.get_vectorizer()

# 根据向量化器中的信息初始化分类器
# 其中,input_dim 是姓氏词汇表的大小,output_dim 是国籍词汇表的大小
classifier = SurnameClassifier(
    input_dim=len(vectorizer.surname_vocab),  # 姓氏词汇表的大小
    hidden_dim=args.hidden_dim,  # 隐藏层维度
    output_dim=len(vectorizer.nationality_vocab)  # 国籍词汇表的大小
)

 # 将模型和类别权重移动到指定的设备(CPU或GPU)
classifier = classifier.to(args.device)
dataset.class_weights = dataset.class_weights.to(args.device)

# 初始化损失函数,使用CrossEntropyLoss,并传入类别权重
loss_func = nn.CrossEntropyLoss(dataset.class_weights)

# 初始化优化器,使用Adam算法
optimizer = optim.Adam(classifier.parameters(), lr=args.learning_rate)

# 初始化学习率调度器,使用ReduceLROnPlateau根据验证损失减少学习率
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer=optimizer, mode='min', factor=0.5, patience=1)

# 创建训练状态字典
train_state = make_train_state(args)

# 初始化训练进度条
epoch_bar = tqdm_notebook(desc='training routine', total=args.num_epochs, position=0)

# 设置数据集为训练分划并初始化训练进度条
dataset.set_split('train')
train_bar = tqdm_notebook(desc='split=train', total=dataset.get_num_batches(args.batch_size), position=1, leave=True)

# 设置数据集为验证分划并初始化验证进度条
dataset.set_split('val')
val_bar = tqdm_notebook(desc='split=val', total=dataset.get_num_batches(args.batch_size), position=1, leave=True)

try:
    # 遍历指定轮数的训练循环
    for epoch_index in range(args.num_epochs):
        train_state['epoch_index'] = epoch_index

        # 训练阶段
        dataset.set_split('train')
        # 创建训练批次生成器
        batch_generator = generate_batches(dataset, batch_size=args.batch_size, device=args.device)
        running_loss = 0.0  # 初始化训练损失
        running_acc = 0.0  # 初始化训练准确率
        classifier.train()  # 将模型设置为训练模式

        # 遍历训练批次
        for batch_index, batch_dict in enumerate(batch_generator):
            # 清空梯度
            optimizer.zero_grad()

            # 计算模型输出
            y_pred = classifier(batch_dict['x_surname'])

            # 计算损失
            loss = loss_func(y_pred, batch_dict['y_nationality'])
            loss_t = loss.item()  # 获取损失的数值
            running_loss += (loss_t - running_loss) / (batch_index + 1)  # 计算指数加权平均损失

            # 反向传播
            loss.backward()

            # 更新模型参数
            optimizer.step()

            # 计算准确率
            acc_t = compute_accuracy(y_pred, batch_dict['y_nationality'])
            running_acc += (acc_t - running_acc) / (batch_index + 1)  # 计算指数加权平均准确率

            # 更新训练进度条
            train_bar.set_postfix(loss=running_loss, acc=running_acc, epoch=epoch_index)
            train_bar.update()

        # 记录训练损失和准确率
        train_state['train_loss'].append(running_loss)
        train_state['train_acc'].append(running_acc)

        # 验证阶段
        dataset.set_split('val')
        # 创建验证批次生成器
        batch_generator = generate_batches(dataset, batch_size=args.batch_size, device=args.device)
        running_loss = 0.  # 初始化验证损失
        running_acc = 0.  # 初始化验证准确率
        classifier.eval()  # 将模型设置为评估模式

        # 遍历验证批次
        for batch_index, batch_dict in enumerate(batch_generator):
            # 计算模型输出
            y_pred = classifier(batch_dict['x_surname'])

            # 计算损失
            loss = loss_func(y_pred, batch_dict['y_nationality'])
            loss_t = loss.to("cpu").item()  # 将损失移动到CPU并获取数值
            running_loss += (loss_t - running_loss) / (batch_index + 1)  # 计算指数加权平均损失

            # 计算准确率
            acc_t = compute_accuracy(y_pred, batch_dict['y_nationality'])
            running_acc += (acc_t - running_acc) / (batch_index + 1)  # 计算指数加权平均准确率

            # 更新验证进度条
            val_bar.set_postfix(loss=running_loss, acc=running_acc, epoch=epoch_index)
            val_bar.update()

        # 记录验证损失和准确率
        train_state['val_loss'].append(running_loss)
        train_state['val_acc'].append(running_acc)

        # 更新训练状态,包括早期停止判断
        train_state = update_train_state(args=args, model=classifier, train_state=train_state)

        # 根据验证损失调整学习率
        scheduler.step(train_state['val_loss'][-1])

        # 如果触发早期停止,退出循环
        if train_state['stop_early']:
            break

        # 重置训练和验证进度条
        train_bar.n = 0
        val_bar.n = 0
        # 更新epoch进度条
        epoch_bar.update()

except KeyboardInterrupt:
    # 如果用户中断训练(例如使用Ctrl+C),打印退出信息
    print("Exiting loop")

检查训练结果

    # 使用训练过程中保存的最佳模型状态更新分类器
classifier.load_state_dict(torch.load(train_state['model_filename']))

# 将分类器和类别权重移动到指定的设备(CPU或GPU)
classifier = classifier.to(args.device)
dataset.class_weights = dataset.class_weights.to(args.device)

# 初始化损失函数,使用带有类别权重的CrossEntropyLoss
loss_func = nn.CrossEntropyLoss(dataset.class_weights)

# 设置数据集为测试分划
dataset.set_split('test')

# 创建测试批次生成器
batch_generator = generate_batches(dataset, 
                                   batch_size=args.batch_size, 
                                   device=args.device)

# 初始化测试集的损失和准确率
running_loss = 0.
running_acc = 0.

# 将分类器设置为评估模式
classifier.eval()

# 遍历测试批次
for batch_index, batch_dict in enumerate(batch_generator):
    # 计算模型输出
    y_pred = classifier(batch_dict['x_surname'])

    # 计算损失
    loss = loss_func(y_pred, batch_dict['y_nationality'])
    loss_t = loss.item()  # 将损失转换为标量值
    running_loss += (loss_t - running_loss) / (batch_index + 1)  # 计算指数加权平均损失

    # 计算准确率
    acc_t = compute_accuracy(y_pred, batch_dict['y_nationality'])
    running_acc += (acc_t - running_acc) / (batch_index + 1)  # 计算指数加权平均准确率

# 将计算得到的测试损失和准确率存储到训练状态字典中
train_state['test_loss'] = running_loss
train_state['test_acc'] = running_acc
print("Test loss: {};".format(train_state['test_loss']))
print("Test Accuracy: {}".format(train_state['test_acc']))

输入自己的姓氏看看分类器表现如何吧

def predict_topk_nationality(name, classifier, vectorizer, k=5):
    """
    预测名称的前k个最可能的国籍及其概率。
    
    Args:
        name (str): 要预测的名称。
        classifier (SurnameClassifier): 分类器实例。
        vectorizer (SurnameVectorizer): 向量化器实例。
        k (int): 要返回的前k个最可能的国籍。
    
    Returns:
        list: 包含最有可能的国籍和其概率的字典列表。
    """
    # 将名称向量化
    vectorized_name = vectorizer.vectorize(name)
    vectorized_name = torch.tensor(vectorized_name).view(1, -1)
    
    # 获取分类器的预测输出,并应用softmax
    prediction_vector = classifier(vectorized_name, apply_softmax=True)
    
    # 获取概率最高的k个国籍的索引和对应的概率值
    probability_values, indices = torch.topk(prediction_vector, k=k)
    
    # 将概率值和索引从PyTorch张量转换为NumPy数组
    probability_values = probability_values.detach().numpy()[0]
    indices = indices.detach().numpy()[0]
    
    results = []
    for prob_value, index in zip(probability_values, indices):
        # 根据索引查找国籍
        nationality = vectorizer.nationality_vocab.lookup_index(index)
        results.append({'nationality': nationality, 
                        'probability': prob_value})
    
    return results

# 请求用户输入一个要分类的姓氏
new_surname = input("Enter a surname to classify: ")

# 将分类器移动到CPU设备
classifier = classifier.to("cpu")

# 请求用户输入想要看到的前k个预测结果的数量
k = int(input("How many of the top predictions to see? "))

# 如果用户请求的k大于国籍的数量,则调整k的值
if k > len(vectorizer.nationality_vocab):
    print("Sorry! That's more than the # of nationalities we have.. defaulting you to max size :)")
    k = len(vectorizer.nationality_vocab)

# 使用predict_topk_nationality函数获取前k个预测结果
predictions = predict_topk_nationality(new_surname, classifier, vectorizer, k=k)

# 打印预测结果
print("Top {} predictions:".format(k))
print("===================")
for prediction in predictions:
    print("{} -> {} (p={:0.2f})".format(new_surname,
                                        prediction['nationality'],
                                        prediction['probability']))

Enter a surname to classify:  Chang
How many of the top predictions to see?  3

上述代码运行结果:

Top 3 predictions:
===================
Chang -> Korean (p=0.40)
Chang -> Chinese (p=0.29)
Chang -> Irish (p=0.15)

如何系统的去学习大模型LLM ?

作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料 包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来

😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

在这里插入图片描述

四、AI大模型商业化落地方案

img

阶段1:AI大模型时代的基础理解

  • 目标:了解AI大模型的基本概念、发展历程和核心原理。
  • 内容
    • L1.1 人工智能简述与大模型起源
    • L1.2 大模型与通用人工智能
    • L1.3 GPT模型的发展历程
    • L1.4 模型工程
    • L1.4.1 知识大模型
    • L1.4.2 生产大模型
    • L1.4.3 模型工程方法论
    • L1.4.4 模型工程实践
    • L1.5 GPT应用案例

阶段2:AI大模型API应用开发工程

  • 目标:掌握AI大模型API的使用和开发,以及相关的编程技能。
  • 内容
    • L2.1 API接口
    • L2.1.1 OpenAI API接口
    • L2.1.2 Python接口接入
    • L2.1.3 BOT工具类框架
    • L2.1.4 代码示例
    • L2.2 Prompt框架
    • L2.2.1 什么是Prompt
    • L2.2.2 Prompt框架应用现状
    • L2.2.3 基于GPTAS的Prompt框架
    • L2.2.4 Prompt框架与Thought
    • L2.2.5 Prompt框架与提示词
    • L2.3 流水线工程
    • L2.3.1 流水线工程的概念
    • L2.3.2 流水线工程的优点
    • L2.3.3 流水线工程的应用
    • L2.4 总结与展望

阶段3:AI大模型应用架构实践

  • 目标:深入理解AI大模型的应用架构,并能够进行私有化部署。
  • 内容
    • L3.1 Agent模型框架
    • L3.1.1 Agent模型框架的设计理念
    • L3.1.2 Agent模型框架的核心组件
    • L3.1.3 Agent模型框架的实现细节
    • L3.2 MetaGPT
    • L3.2.1 MetaGPT的基本概念
    • L3.2.2 MetaGPT的工作原理
    • L3.2.3 MetaGPT的应用场景
    • L3.3 ChatGLM
    • L3.3.1 ChatGLM的特点
    • L3.3.2 ChatGLM的开发环境
    • L3.3.3 ChatGLM的使用示例
    • L3.4 LLAMA
    • L3.4.1 LLAMA的特点
    • L3.4.2 LLAMA的开发环境
    • L3.4.3 LLAMA的使用示例
    • L3.5 其他大模型介绍

阶段4:AI大模型私有化部署

  • 目标:掌握多种AI大模型的私有化部署,包括多模态和特定领域模型。
  • 内容
    • L4.1 模型私有化部署概述
    • L4.2 模型私有化部署的关键技术
    • L4.3 模型私有化部署的实施步骤
    • L4.4 模型私有化部署的应用场景

学习计划:

  • 阶段1:1-2个月,建立AI大模型的基础知识体系。
  • 阶段2:2-3个月,专注于API应用开发能力的提升。
  • 阶段3:3-4个月,深入实践AI大模型的应用架构和私有化部署。
  • 阶段4:4-5个月,专注于高级模型的应用和部署。
这份完整版的大模型 LLM 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

😝有需要的小伙伴,可以Vx扫描下方二维码免费领取🆓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/745596.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI智能体的炒作与现实:GPT-4都撑不起,现实任务成功率不到15%

AI 智能体的宣传很好&#xff0c;现实不太妙。 随着大语言模型的不断进化与自我革新&#xff0c;性能、准确度、稳定性都有了大幅的提升&#xff0c;这已经被各个基准问题集验证过了。 但是&#xff0c;对于现有版本的 LLM 来说&#xff0c;它们的综合能力似乎并不能完全支撑得…

Java基础的重点知识-04-封装

文章目录 面向对象思想封装 面向对象思想 在计算机程序设计过程中&#xff0c;参照现实中事物&#xff0c;将事物的属性特征、行为特征抽象出来&#xff0c;描述成计算机事件的设计思想。 面向对象思想的三大基本特征: 封装、继承、多态 1.类和对象 类是对象的抽象&#xff…

初阶 《操作符详解》11. 下标引用、函数调用和结构成员

11. 下标引用、函数调用和结构成员 1. [ ] 下标引用操作符 操作数&#xff1a;一个数组名 一个索引值 int arr[10];//创建数组 arr[9] 10;//实用下标引用操作符&#xff0c;[ ]的两个操作数是arr和9arr[7]-->*(arr7)-->*(7arr)-->7[arr] 7[arr] 9; //编译器不会…

软考《信息系统运行管理员》-1.1信息系统概述

1.1信息系统概述 信息的含义 信息是人们关心的事情的消息或知识 信息的分类 按产生信息的客体性质分&#xff1a; 自然信息&#xff1a;声、光、热、电等生物信息&#xff1a;遗传信息&#xff0c;生物体内、生物种群内的信息交流机器信息&#xff1a;自动控制系统社会信息…

flask与vue实现通过websocket通信

在一些情况下&#xff0c;我们需要实现前后端之间的时刻监听&#xff0c;本文是一篇工具文档&#xff0c;用于解决前后端之间使用websocket交互。 一. Flask的相关配置 1. 下载相关依赖库 如果还没有配置flask的话&#xff0c;需要先安装flask,同时为解决跨域问题&#xff0…

C++学习全教程(day1:变量和数据类型、运算符、流行控制语句)

目录 一、变量和数据类型 1、变量 2、标识符 3、作用域 4、常量 5、数据类型 1.基本数据类型-整型 2.char类型 3.bool类型 4.浮点类型 5.字面值常量 二、运算符 1、算数运算符 2、赋值运算符 3、复合赋值运算符 4、关系和逻辑运算 1.关系运算符 -------结果是…

SAP系统中如何用事务码图形视图寻找MD04增强开发实施点

在之前发布的文章中&#xff0c;介绍了善用事务码的图形视图以观察事务的执行流程以及如何在MD04中实施增强以改变生产订单的显示顺序。本文结合两者&#xff0c;介绍一下如何利用事务码的图形视图找到增强开发的实施点。 在事务码中输入SE93&#xff0c;进入图形视图&#xf…

sql:between and日期毫秒精度过多导致的查询bug

复现 一般情况下&#xff0c;前端传的日期值大多都是yyyy-MM-dd HH:mm:ss(标准格式)&#xff0c;比如2024-06-25 10:49:50&#xff0c;但是在测试环境&#xff0c;测试人员测出了一个带毫秒的日期&#xff1a;比如2024-06-25 10:49:50.9999999 这种情况下会出现查询bug SELEC…

使用 shell 脚本同步 yum 源建立本地私有仓库

文章目录 [toc]事出有因脚本内容前端展示 事出有因 有两方面原因做了这个事情&#xff1a; dockerhub 国内无法访问centos 7 要停止社区支持了 结合两个情况&#xff0c;不久的将来&#xff0c;可能国内也就没有对应的 yum 仓库了&#xff08;现在想找 centos 7 之前的仓库&…

企业数据挖掘平台产品特色及合作案例介绍

泰迪企业数据挖掘平台是一款通用的、企业级、智能化的数据分析模型构建与数据应用场景设计工具&#xff0c;能够一体化地完成数据集成、模型构建、模型发布&#xff0c;为数据分析、探索、服务流程提供支撑&#xff0c;提供完整的数据探索、多数据源接入、特征处理、模型搭建、…

【Python机器学习】聚类算法的对比与评估——在人脸数据集上比较算法

数据探查&#xff1a; 我们将k均值、DBSCAN和凝聚聚类算法应用于Wild数据集中的Labeled Faces&#xff0c;并查看它们是否找到了有趣的结构。我们将使用数据的特征脸表示&#xff0c;它由包含100个成分的PCA(whitenTrue)生成&#xff1a; peoplefetch_lfw_people(data_home &…

为了提高出图效率,我做了一款可视化制作气泡图谱的小工具

嗨&#xff0c;大家好&#xff0c;我是徐小夕&#xff0c;之前和大家分享了很多可视化低代码的最佳实践&#xff0c;今天和大家分享一下我基于实际场景开发的小工具——BubbleMap。 demo地址&#xff1a;http://wep.turntip.cn/design/bubbleMap 开发背景 之前在公司做图表开发…

LINUX操作系统:Mx Linux,用虚拟机VMware Workstation安装体验

需求说明&#xff1a; 操作系统目前流行有Windows、Linux、Unix等&#xff0c;中国人应该要知道国有操作系统&#xff0c;也要支持国产操作系统&#xff0c;为了更好支持国产操作系统&#xff0c;我们也要知己知彼&#xff0c;那么今天就来体验一把操作系统Mx_Linux_23.2的安装…

mac m芯片下安装nacos

背景&#xff1a;最近再研究 下载地址&#xff1a; https://nacos.io/download/nacos-server/ 解压zip包 unzip nacos-server-2.3.2.zip启动 进入到bin目录下 ./startup.sh -m standalone访问可视化界面 账号密码都是nacos&#xff0c;进行登录即可&#xff0c;nacos的端口为…

打破网络通信界限,推动供应链数字化转型

在当前全球经济低迷的背景下&#xff0c;中国经济的发展模式正在转变&#xff0c;从规模扩张到品质提升&#xff0c;通过优势的整合和叠加&#xff0c;释放出新的生产力。2023年前三季度&#xff0c;中国国内生产总值达到91.3万亿元&#xff0c;同比增长了5.2%1&#xff0c;增速…

红酒与珠宝:璀璨与醇香的奢华交响,双重诱惑难挡

在璀璨的灯光下&#xff0c;红酒与珠宝各自闪耀着迷人的光芒&#xff0c;它们如同夜空中的繁星&#xff0c;交相辉映&#xff0c;共同演绎着奢华的双重诱惑。今天&#xff0c;就让我们一起走进这个充满魅力的世界&#xff0c;感受红酒与珠宝带来的无尽魅力。 首先&#xff0c;让…

1966 ssm 流浪猫领养网站系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 流浪猫领养网站系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/…

红酒舞动,运动风采,品味力与美

当夜幕降临&#xff0c;城市的灯火渐次亮起&#xff0c;忙碌了一天的人们开始寻找那份属于自己的宁静与愉悦。在这个时刻&#xff0c;红酒与运动&#xff0c;这两个看似截然不同的元素&#xff0c;却能以它们不同的魅力&#xff0c;为我们带来一场视觉与感官的盛宴。 红酒&…

【八股系列】Vue中的<keep-alive>组件:深入解析与实践指南

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【探索响应式布局的奥秘&#xff1a;关键技术与实战代码示例】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&#x1f44d;收藏⭐评论…