52、深度学习-自学之路-自己搭建深度学习框架-13、对话预测功能,使用我们自己建的架构重写RNN预测网络,程序的详细解读。

复制代码
import numpy as np


class Tensor(object):

    def __init__(self, data,
                 autograd=False,
                 creators=None,
                 creation_op=None,
                 id=None):

        self.data = np.array(data)
        self.autograd = autograd
        self.grad = None
        if (id is None):
            self.id = np.random.randint(0, 100000)
        else:
            self.id = id

        self.creators = creators
        self.creation_op = creation_op
        self.children = {}

        if (creators is not None):
            for c in creators:
                if (self.id not in c.children):
                    c.children[self.id] = 1
                else:
                    c.children[self.id] += 1

    def all_children_grads_accounted_for(self):
        for id, cnt in self.children.items():
            if (cnt != 0):
                return False
        return True

    def backward(self, grad=None, grad_origin=None):
        if (self.autograd):

            if (grad is None):
                grad = Tensor(np.ones_like(self.data))

            if (grad_origin is not None):
                if (self.children[grad_origin.id] == 0):
                    raise Exception("cannot backprop more than once")
                else:
                    self.children[grad_origin.id] -= 1

            if (self.grad is None):
                self.grad = grad
            else:
                self.grad += grad

            # grads must not have grads of their own
            assert grad.autograd == False

            # only continue backpropping if there's something to
            # backprop into and if all gradients (from children)
            # are accounted for override waiting for children if
            # "backprop" was called on this variable directly
            if (self.creators is not None and
                    (self.all_children_grads_accounted_for() or
                     grad_origin is None)):

                if (self.creation_op == "add"):
                    self.creators[0].backward(self.grad, self)
                    self.creators[1].backward(self.grad, self)

                if (self.creation_op == "sub"):
                    self.creators[0].backward(Tensor(self.grad.data), self)
                    self.creators[1].backward(Tensor(self.grad.__neg__().data), self)

                if (self.creation_op == "mul"):
                    new = self.grad * self.creators[1]
                    self.creators[0].backward(new, self)
                    new = self.grad * self.creators[0]
                    self.creators[1].backward(new, self)

                if (self.creation_op == "mm"):
                    c0 = self.creators[0]
                    c1 = self.creators[1]
                    new = self.grad.mm(c1.transpose())
                    c0.backward(new)
                    new = self.grad.transpose().mm(c0).transpose()
                    c1.backward(new)

                if (self.creation_op == "transpose"):
                    self.creators[0].backward(self.grad.transpose())

                if ("sum" in self.creation_op):
                    dim = int(self.creation_op.split("_")[1])
                    self.creators[0].backward(self.grad.expand(dim,
                                                               self.creators[0].data.shape[dim]))

                if ("expand" in self.creation_op):
                    dim = int(self.creation_op.split("_")[1])
                    self.creators[0].backward(self.grad.sum(dim))

                if (self.creation_op == "neg"):
                    self.creators[0].backward(self.grad.__neg__())

                if (self.creation_op == "sigmoid"):
                    ones = Tensor(np.ones_like(self.grad.data))
                    self.creators[0].backward(self.grad * (self * (ones - self)))

                if (self.creation_op == "tanh"):
                    ones = Tensor(np.ones_like(self.grad.data))
                    self.creators[0].backward(self.grad * (ones - (self * self)))

                if (self.creation_op == "index_select"):
                    new_grad = np.zeros_like(self.creators[0].data)
                    indices_ = self.index_select_indices.data.flatten()
                    grad_ = grad.data.reshape(len(indices_), -1)
                    for i in range(len(indices_)):
                        new_grad[indices_[i]] += grad_[i]
                    self.creators[0].backward(Tensor(new_grad))

                if (self.creation_op == "cross_entropy"):
                    dx = self.softmax_output - self.target_dist
                    self.creators[0].backward(Tensor(dx))

    def __add__(self, other):
        if (self.autograd and other.autograd):
            return Tensor(self.data + other.data,
                          autograd=True,
                          creators=[self, other],
                          creation_op="add")
        return Tensor(self.data + other.data)

    def __neg__(self):
        if (self.autograd):
            return Tensor(self.data * -1,
                          autograd=True,
                          creators=[self],
                          creation_op="neg")
        return Tensor(self.data * -1)

    def __sub__(self, other):
        if (self.autograd and other.autograd):
            return Tensor(self.data - other.data,
                          autograd=True,
                          creators=[self, other],
                          creation_op="sub")
        return Tensor(self.data - other.data)

    def __mul__(self, other):
        if (self.autograd and other.autograd):
            return Tensor(self.data * other.data,
                          autograd=True,
                          creators=[self, other],
                          creation_op="mul")
        return Tensor(self.data * other.data)

    def sum(self, dim):
        if (self.autograd):
            return Tensor(self.data.sum(dim),
                          autograd=True,
                          creators=[self],
                          creation_op="sum_" + str(dim))
        return Tensor(self.data.sum(dim))

    def expand(self, dim, copies):

        trans_cmd = list(range(0, len(self.data.shape)))
        trans_cmd.insert(dim, len(self.data.shape))
        new_data = self.data.repeat(copies).reshape(list(self.data.shape) + [copies]).transpose(trans_cmd)

        if (self.autograd):
            return Tensor(new_data,
                          autograd=True,
                          creators=[self],
                          creation_op="expand_" + str(dim))
        return Tensor(new_data)

    def transpose(self):
        if (self.autograd):
            return Tensor(self.data.transpose(),
                          autograd=True,
                          creators=[self],
                          creation_op="transpose")

        return Tensor(self.data.transpose())

    def mm(self, x):
        if (self.autograd):
            return Tensor(self.data.dot(x.data),
                          autograd=True,
                          creators=[self, x],
                          creation_op="mm")
        return Tensor(self.data.dot(x.data))

    def sigmoid(self):
        if (self.autograd):
            return Tensor(1 / (1 + np.exp(-self.data)),
                          autograd=True,
                          creators=[self],
                          creation_op="sigmoid")
        return Tensor(1 / (1 + np.exp(-self.data)))

    def tanh(self):
        if (self.autograd):
            return Tensor(np.tanh(self.data),
                          autograd=True,
                          creators=[self],
                          creation_op="tanh")
        return Tensor(np.tanh(self.data))

    def index_select(self, indices):

        if (self.autograd):
            new = Tensor(self.data[indices.data],
                         autograd=True,
                         creators=[self],
                         creation_op="index_select")
            new.index_select_indices = indices
            return new
        return Tensor(self.data[indices.data])

    def cross_entropy(self, target_indices):

        temp = np.exp(self.data)
        softmax_output = temp / np.sum(temp,
                                       axis=len(self.data.shape) - 1,
                                       keepdims=True)

        t = target_indices.data.flatten()
        p = softmax_output.reshape(len(t), -1)
        target_dist = np.eye(p.shape[1])[t]
        loss = -(np.log(p) * (target_dist)).sum(1).mean()

        if (self.autograd):
            out = Tensor(loss,
                         autograd=True,
                         creators=[self],
                         creation_op="cross_entropy")
            out.softmax_output = softmax_output
            out.target_dist = target_dist
            return out

        return Tensor(loss)

    def __repr__(self):
        return str(self.data.__repr__())

    def __str__(self):
        return str(self.data.__str__())
class Layer(object):

    def __init__(self):
        self.parameters = list()

    def get_parameters(self):
        return self.parameters

class Tanh(Layer):
    def __init__(self):
        super().__init__()

    def forward(self, input):
        return input.tanh()


class Sigmoid(Layer):
    def __init__(self):
        super().__init__()

    def forward(self, input):
        return input.sigmoid()

'''
损失函数(CrossEntropyLoss)
计算交叉熵损失。
功能:定义一个交叉熵损失函数。
作用:计算模型预测值与真实标签之间的交叉熵损失。
'''
class CrossEntropyLoss(object):

    def __init__(self):    #功能:调用父类的初始化方法。
        super().__init__() #作用:确保 CrossEntropyLoss 类继承了父类的属性和方法。

    '''
    input:模型的输出,通常是一个 Tensor 对象,表示预测的概率分布。
    target:真实标签,通常是一个 Tensor 对象,表示真实的类别索引。
    
    调用 cross_entropy 方法
    input.cross_entropy(target):
    调用 input 的 cross_entropy 方法,计算交叉熵损失。
    这里的 cross_entropy 方法需要在 Tensor 类中实现。
    
    返回值
    功能:返回交叉熵损失值。
    作用:用于衡量模型预测值与真实标签之间的差异。 
    '''
    def forward(self, input, target):
        return input.cross_entropy(target)

class Sequential(Layer):

    def __init__(self, layers=list()):
        super().__init__()

        self.layers = layers

    def add(self, layer):
        self.layers.append(layer)

    def forward(self, input):
        for layer in self.layers:
            input = layer.forward(input)
        return input

    def get_parameters(self):
        params = list()
        for l in self.layers:
            params += l.get_parameters()
        return params
'''
(2.1) 嵌入层(Embedding)
将词的索引映射为固定维度的向量。
index_select 方法根据输入索引选择对应的嵌入向量。
说明:
Embedding 类是深度学习中的一个重要组件,用于将离散的符号(如单词、类别等)映射到连续的向量空间。
这种映射通常被称为词嵌入(Word Embedding),是自然语言处理(NLP)任务中的基础操作。
下面我会详细讲解 Embedding 类的实现和功能。
'''
class Embedding(Layer):  #1. 类的定义 功能:定义一个嵌入层,继承自 Layer 类。 作用:将离散的符号(如单词索引)映射到连续的向量空间。

    def __init__(self, vocab_size, dim):   #2. 初始化方法:__init__
        super().__init__()           #(1) super().__init__() 功能:调用父类 Layer 的初始化方法。 作用:确保 Embedding 类继承了 Layer 类的属性和方法。

        self.vocab_size = vocab_size #(2)self.vocab_size = vocab_size  #功能:存储词汇表的大小。  #作用:词汇表的大小决定了嵌入矩阵的行数。
        self.dim = dim               #(3) self.dim = dim

        #功能:初始化嵌入矩阵。
        #细节:
        # np.random.rand(vocab_size, dim):生成一个形状为 (vocab_size, dim) 的随机矩阵,元素值在 [0, 1) 之间。
        # - 0.5:将元素值调整到 [-0.5, 0.5) 之间。
        # / dim:将元素值缩放到 [-0.5/dim, 0.5/dim) 之间,这是一种常见的初始化方法。
        # Tensor(..., autograd=True):将矩阵封装为 Tensor 对象,并启用自动求导。
        # this random initialiation style is just a convention from word2vec
        self.weight = Tensor((np.random.rand(vocab_size, dim) - 0.5) / dim, autograd=True)

        #功能:将嵌入矩阵添加到模型的参数列表中。
        #作用:在训练过程中,优化器会更新这些参数。
        self.parameters.append(self.weight)
    #3. 前向传播方法:forward
    # (1) 输入参数 input
    # 功能:接受输入数据。
    # 类型:input 是一个 Tensor 对象,通常包含词的索引。
    def forward(self, input):
        return self.weight.index_select(input)
    '''
    self.weight.index_select(input)
    功能:根据输入索引从嵌入矩阵中选择对应的向量。
    细节:
    self.weight 是嵌入矩阵,形状为 (vocab_size, dim)。    
    input 是词的索引,形状为 (batch_size, sequence_length) 或 (batch_size,)。    
    index_select 方法根据索引从嵌入矩阵中选择对应的行(即词向量)
    (3) 返回值
    功能:返回选择的词向量。    
    作用:这些词向量可以作为后续层的输入。
    '''

class Linear(Layer):

    def __init__(self, n_inputs, n_outputs):
        super().__init__()
        W = np.random.randn(n_inputs, n_outputs) * np.sqrt(2.0 / (n_inputs))
        self.weight = Tensor(W, autograd=True)
        self.bias = Tensor(np.zeros(n_outputs), autograd=True)

        self.parameters.append(self.weight)
        self.parameters.append(self.bias)

    def forward(self, input):
        return input.mm(self.weight) + self.bias.expand(0, len(input.data))

class MSELoss(Layer):

    def __init__(self):
        super().__init__()

    def forward(self, pred, target):
        return ((pred - target) * (pred - target)).sum(0)

'''
 优化器(SGD)
 实现随机梯度下降(SGD)优化器。
'''
class SGD(object):

    def __init__(self, parameters, alpha=0.1):
        self.parameters = parameters
        self.alpha = alpha

    def zero(self):
        for p in self.parameters:
            p.grad.data *= 0

    def step(self, zero=True):

        for p in self.parameters:

            p.data -= p.grad.data * self.alpha

            if (zero):
                p.grad.data *= 0

'''
(2.2) RNN 单元(RNNCell)
实现了一个简单的 RNN 单元。
forward 方法计算当前时间步的输出和隐藏状态。
init_hidden 方法初始化隐藏状态。
'''
class RNNCell(Layer): #1. 类的定义   #功能:定义一个 RNN 单元,继承自 Layer 类。  #作用:处理序列数据,维护隐藏状态,并输出当前时间步的结果。

    def __init__(self, n_inputs, n_hidden, n_output, activation='sigmoid'):  #初始化方法:__init__
        super().__init__()       #功能:调用父类 Layer 的初始化方法。  作用:确保 RNNCell 类继承了 Layer 类的属性和方法。

        self.n_inputs = n_inputs #n_inputs:输入数据的维度。
        self.n_hidden = n_hidden #n_hidden:隐藏状态的维度。
        self.n_output = n_output #n_output:输出数据的维度。

        '''
        activation:激活函数类型(支持 sigmoid 和 tanh)。
        self.activation:根据 activation 参数选择激活函数。
        Sigmoid():Sigmoid 激活函数。        
        Tanh():Tanh 激活函数。
        '''
        if (activation == 'sigmoid'):
            self.activation = Sigmoid()
        elif (activation == 'tanh'):
            self.activation == Tanh()
        else:
            raise Exception("Non-linearity not found")


        self.w_ih = Linear(n_inputs, n_hidden)  #self.w_ih:输入到隐藏状态的线性变换层,形状为 (n_inputs, n_hidden)。
        self.w_hh = Linear(n_hidden, n_hidden)  #self.w_hh:隐藏状态到隐藏状态的线性变换层,形状为 (n_hidden, n_hidden)。
        self.w_ho = Linear(n_hidden, n_output)  #self.w_ho:隐藏状态到输出的线性变换层,形状为 (n_hidden, n_output)。
        '''
        self.parameters:将 w_ih、w_hh 和 w_ho 的参数添加到模型的参数列表中。
        作用:在训练过程中,优化器会更新这些参数。
        '''
        self.parameters += self.w_ih.get_parameters()  #
        self.parameters += self.w_hh.get_parameters()  #
        self.parameters += self.w_ho.get_parameters()  #
    '''
    输入参数:
    input:当前时间步的输入数据,形状为 (batch_size, n_inputs)。
    hidden:上一个时间步的隐藏状态,形状为 (batch_size, n_hidden)。
    '''

    def forward(self, input, hidden):
        '''
        计算隐藏状态
        '''
        from_prev_hidden = self.w_hh.forward(hidden)            # 将上一个时间步的隐藏状态 hidden 通过线性变换 w_hh 映射到当前时间步的隐藏状态空间。
        combined = self.w_ih.forward(input) + from_prev_hidden  # 将当前时间步的输入 input 通过线性变换 w_ih 映射到隐藏状态空间,并与 from_prev_hidden 相加。
        new_hidden = self.activation.forward(combined)          # 对相加后的结果应用激活函数,得到当前时间步的隐藏状态 new_hidden
        output = self.w_ho.forward(new_hidden)                  # 将当前时间步的隐藏状态 new_hidden 通过线性变换 w_ho 映射到输出空间。
        return output, new_hidden                               # output:当前时间步的输出,形状为 (batch_size, n_output)。 new_hidden:当前时间步的隐藏状态,形状为 (batch_size, n_hidden)。

    # 初始化隐藏状态方法:init_hidden
    # 作用:初始化隐藏状态。
    # 返回值:一个全零的 Tensor,形状为 (batch_size, n_hidden)。
    def init_hidden(self, batch_size=1): #batch_size:批量大小,默认为 1
        return Tensor(np.zeros((batch_size, self.n_hidden)), autograd=True)


import sys, random, math
from collections import Counter
import numpy as np


'''1. 数据预处理
(1.1) 读取数据
从文件中读取数据,并将每一行存储到 raw 列表中。
'''
f = open('qa1_single-supporting-fact_train.txt', 'r')
raw = f.readlines()
f.close()

'''
(1.2) 分词
将每一行文本转换为小写,并去除换行符。
使用空格分词,并忽略每行的第一个词(假设第一个词是无关的)。
'''
tokens = list()
for line in raw[0:1000]:
    tokens.append(line.lower().replace("\n", "").split(" ")[1:])
'''
(1.3) 填充序列
将每行文本填充到固定长度(6 个词),不足的部分用 - 填充。  
这个部分应该是和此列表相结合的,因为每句话都是不超过6个单词,所以这里面把6个单词作为一句话来处理。
'''
new_tokens = list()
for line in tokens:
    new_tokens.append(['-'] * (6 - len(line)) + line)

tokens = new_tokens

'''
(1.4) 构建词汇表
使用集合 set 去重,构建词汇表。
将集合转换为列表 vocab。
'''
vocab = set()
for sent in tokens:
    for word in sent:
        vocab.add(word)

vocab = list(vocab)

'''
(1.5) 构建词到索引的映射
为每个词分配一个唯一的索引,构建 word2index 字典。
'''
word2index = {}
for i, word in enumerate(vocab):
    word2index[word] = i


def words2indices(sentence):
    idx = list()
    for word in sentence:
        idx.append(word2index[word])
    return idx

'''
(1.6) 将文本转换为索引
将每行文本转换为对应的索引列表。
'''
indices = list()
for line in tokens:
    idx = list()
    for w in line:
        idx.append(word2index[w])
    indices.append(idx)

'''
(1.7) 转换为 NumPy 数组
'''
data = np.array(indices)

'''
2. 模型定义
初始化模型和优化器  初始化嵌入层、RNN 单元、损失函数和优化器
'''
embed = Embedding(vocab_size=len(vocab),dim=16)
model = RNNCell(n_inputs=16, n_hidden=16, n_output=len(vocab))

criterion = CrossEntropyLoss()
optim = SGD(parameters=model.get_parameters() + embed.get_parameters(), alpha=0.05)


'''
 训练循环 
 使用批量数据进行训练。
 每个时间步计算输出和隐藏状态。
 计算损失并反向传播。
 更新模型参数
'''
for iter in range(1000):
    batch_size = 100
    total_loss = 0

    hidden = model.init_hidden(batch_size=batch_size)


    for t in range(5):
        input = Tensor(data[0:batch_size, t], autograd=True)
        rnn_input = embed.forward(input=input)
        output, hidden = model.forward(input=rnn_input, hidden=hidden)

    target = Tensor(data[0:batch_size, t + 1], autograd=True)
    loss = criterion.forward(output, target)
    loss.backward()
    optim.step()
    total_loss += loss.data
    if (iter % 200 == 0):
        p_correct = (target.data == np.argmax(output.data, axis=1)).mean()
        print("Loss:", total_loss / (len(data) / batch_size), "% Correct:", p_correct)

#初始化隐藏状态
batch_size = 1
hidden = model.init_hidden(batch_size=batch_size)
# 计算输出
for t in range(5):
    input = Tensor(data[0:batch_size,t], autograd=True)
    rnn_input = embed.forward(input=input)
    output, hidden = model.forward(input=rnn_input, hidden=hidden)

target = Tensor(data[0:batch_size,t+1], autograd=True)
loss = criterion.forward(output, target)

# 打印结果
ctx = ""
for idx in data[0:batch_size][0][0:-1]:
    ctx += vocab[idx] + " "
print("Context:",ctx)
print("True:",vocab[target.data[0]])
print("Pred:", vocab[output.data.argmax()])


''' 第一次
Loss: 0.4680828278085011 % Correct: 0.0
Loss: 0.17895626941023882 % Correct: 0.23
Loss: 0.1606657974044729 % Correct: 0.3
Loss: 0.1481854218501178 % Correct: 0.32
Loss: 0.13960603129533444 % Correct: 0.35
Context: - mary moved to the 
True: bathroom.
Pred: bathroom.
'''

'''第二次
Loss: 0.4554923906553056 % Correct: 0.01
Loss: 0.17450458457970364 % Correct: 0.23
Loss: 0.1537305632182028 % Correct: 0.33
Loss: 0.13882016326307411 % Correct: 0.36
Loss: 0.13465901151417053 % Correct: 0.37
Context: - mary moved to the 
True: bathroom.
Pred: office.
'''

'''第三次
Loss: 0.45696131353100666 % Correct: 0.12
Loss: 0.17446651127257118 % Correct: 0.27
Loss: 0.16225291144270232 % Correct: 0.28
Loss: 0.1417173151945064 % Correct: 0.34
Loss: 0.13637942677769582 % Correct: 0.37
Context: - mary moved to the 
True: bathroom.
Pred: hallway.
'''

'''第四次
Loss: 0.4449260906841651 % Correct: 0.0
Loss: 0.1782109486619849 % Correct: 0.23
Loss: 0.1496331404381601 % Correct: 0.35
Loss: 0.14350842163988237 % Correct: 0.34
Loss: 0.13665930525935824 % Correct: 0.37
Context: - mary moved to the 
True: bathroom.
Pred: hallway.
'''

'''第五次
Loss: 0.45827573579339315 % Correct: 0.0
Loss: 0.1756007557865982 % Correct: 0.23
Loss: 0.15933848432214442 % Correct: 0.31
Loss: 0.142949504390499 % Correct: 0.34
Loss: 0.13783751879604417 % Correct: 0.35
Context: - mary moved to the 
True: bathroom.
Pred: office.
'''

'''
总结:虽然预测值和真实值有差距,但是在整体的句式上是正确的。填写的次是一个正确的词,不想以前那么的混乱了。
'''
相关推荐
北岛寒沫2 小时前
深度学习奠基作 AlexNet 论文阅读笔记(2025.2.25)
论文阅读·笔记·深度学习
青衫一笔墨3 小时前
企业级大模型应用的Java-Python异构融合架构实践
java·人工智能·python·架构
YANQ6623 小时前
4. designer建立关于目标检测的简单界面(python)
人工智能·python·目标检测
learner_ctr3 小时前
AI手机的技术细节
人工智能·智能手机
jikuaidi6yuan3 小时前
计算机视觉(CV)
人工智能
IT猿手4 小时前
智能优化算法:雪橇犬优化算法(Sled Dog Optimizer,SDO)求解23个经典函数测试集,MATLAB
开发语言·前端·人工智能·算法·机器学习·matlab
伟贤AI之路4 小时前
从零到一:如何用阿里云百炼和火山引擎搭建专属 AI 助手(DeepSeek)?
人工智能·阿里云·火山引擎
lzq6034 小时前
2025年2月科技热点深度解析:AI竞赛、量子突破与开源革命
人工智能
forestsea5 小时前
DeepSeek 提示词:基础结构
人工智能·提示词·deepseek