使用go语言构建区块链 Part4.事务1

这篇具有很好参考价值的文章主要介绍了使用go语言构建区块链 Part4.事务1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

英文源地址

简介

事务是比特币的核心, 区块链的唯一目的是以安全可靠的方式存储交易, 因此在交易创建后没有人可以修改. 今天我们开始实现事务, 但由于这是一个相当大的主题, 我将它分成两部分: 在这一部分中, 我们将实现事务的通用机制, 在第二部分中, 我们将研究细节.
此外, 由于代码的变化是巨大的, 在这里描述它们是没有意义的. 你可以在这里查看到所有的变化.

There is no spoon(黑客帝国台词)

如果你曾经开发过一个web应用程序, 为了实现支付, 你可能会在数据库中创建这些表: 账户和交易.一个账户将存储有关用户的信息, 包括他们的个人信息和余额, 一次交易将存储一个账户到另一个账户的资金转移的信息. 在比特币中, 交易以完全不同的方式实现. 它们是:

  1. 没有账户
  2. 没有余额
  3. 没有地址
  4. 没有硬币
  5. 没有发送者和接收者

由于区块链是一个公开和开放的数据库, 我们不想存储关于钱包所有者的任何敏感信息.硬币不在账户中收纳. 交易不会把钱从一个地址转移到另一个地址.没有保存账户余额的字段或属性. 只有交易, 但是交易里面有什么呢?

比特币交易
交易是输入和输出的组合

type Transaction struct {
	ID   []byte
	Vin  []TXInput
	Vout []TXOutput
}

对于每一笔新的交易, 它的输入会引用前一笔交易的输出(这里有个例外, coinbase交易), 引用就是花费的意思.所谓引用之前的一个输入, 也就是将之前的一个输出包含在另一笔交易的输入当中, 就是花费之前的交易输出. 交易的输出, 就是币实际存储的地方. 下面的图示阐释了交易之间的互相关联.
使用go语言构建区块链 Part4.事务1,Design Database,BlockChain,Golang,区块链,golang,开发语言
需要注意的是:

  1. 有一些输出并没有被关联到某个输入上
  2. 一笔交易的输入可以引用之前多笔交易的输出
  3. 一个输入必须引用一个输出

贯穿全文, 我们将会使用像’money’, ‘coin’, ‘spend’, ‘send’, 'account’等等这样的词. 但是在比特币中, 其实并不存在这些概念. 交易仅仅是通过一个脚本(script)来锁定(lock)一些值(value), 而这些值只可以被锁定它们的人解锁(unlock).
(每一笔比特币交易都会创造输出,输出都会被区块链记录下来。给某个人发送比特币,实际上意味着创造新的 UTXO 并注册到那个人的地址,可以为他所用。)

交易输出

先从输出开始

type TXOutput struct {
	Value        int
	ScriptPubKey string
}

输出主要包含两部分:

  1. 一定量的比特币(Value)
  2. 一个锁定脚本(ScriptPubKey), 要花这笔钱, 必须解锁该脚本

实际上, 正式输出里面存储了’币’(注意, 也就是上面的Value字段). 而这里的存储, 指的是用一个数学难题对输出进行锁定, 这个难题被存储在ScriptPubKey里面. 在内部, 比特币使用了一个叫做Script的脚本语言, 用它来定义锁定和解锁输出的逻辑. 虽然这个语言相当的原始(这是为了避免潜在的黑客攻击和滥用而有意为之), 并不复杂, 但是我们也并不会讨论它的细节. 你可以在这里找到详细解释.

在比特币中, value字段存储的是satoshi的数量, 而不是BTC的数量. 一个satoshi等于一亿分之一的BTC(0.00000001BTC), 这也是比特币里面最小的货币单位(就像是一分的硬币)

由于没有实现地址(address), 所以目前我们会避免涉及逻辑相关的完整脚本. ScriptPubKey将会存储一个任意的字符串(用户定义的钱包地址).

顺表说一下, 有了一个这样的脚本语言, 也意味着比特币其实可以作为一个智能合约平台.

关于输出, 非常重要的一点是: 它们是不可再分的(indivisible). 也就是说, 你无法仅引用其中的一部分. 要么不用, 如果要用, 必须一次性用完. 当一个新的交易中引用了某个输出, 那么这个输出必须被全部花费. 如果它的值比需要的值大, 那么就会产生一个找零, 找零会返还给发送方.这跟现实世界的场景十分类似, 当你像要支付时, 如果一个东西值1美元, 而你给了一个5美元的纸币, 那么你会得到一个4美元的找零.

交易输入

这里是输入

type TXInput struct {
	Txid      []byte
	Vout      int
	ScriptSig string
}

正如之前所提到的, 一个输入引用了一个输出: Txid存储的时之前交易的ID, Vout存储的时该输出在那笔交易中所有输出的索引(因为一笔交易可以有多个输出, 需要有信息指明时具体的哪一个).ScriptSig是一个脚本, 提供了可解锁输出结构里面ScriptPubKey字段的数据. 如果ScriptSig提供的数据是正确的, 那么输出就会解锁, 然后被解锁的值可以被用于产生新的输出; 如果数据不正确, 输出就无法被引用在输入中, 或者说, 无法使用这个输出. 这种机制, 保证了用户无法花费属于其他人的币.
再次强调, 由于我们还没有实现地址, 所以目前ScriptSig将仅仅存储一个用户自定义的任意钱包地址. 我们会在下一篇文章中实现公钥(public key)和签名(signature).
来简要总结一下.输出, 就是’币’存储的地方. 每个输出都会带有一个解锁脚本, 这个脚本定义了解锁该输出的逻辑. 每笔新的交易, 必须至少有一个输入与输出. 一个输入引用了之前一笔的输出, 并提供了解锁数据(也就是ScriptSig字段), 该数据会被用于在输出的解锁脚本中解锁输出, 解锁完成后即可使用它的值去产生新的输出.
每一笔输入都是之前一笔交易的输出, 那么假设从某一笔交易开始不断往前追溯, 它所涉及的输入和输出到底是谁先存在呢?换个说法, 这是个鸡和蛋谁先谁后的问题, 是先有蛋还是先有鸡呢?

先有蛋

在比特币中, 是先有蛋, 然后才有鸡的. 输入引用输出的逻辑, 是经典的’蛋还是鸡’的问题: 输入先产生输出, 然后输出使得输入成为可能. 在比特币中, 最先有输出, 然后才有输入. 换而言之, 每一笔交易只有输出, 没有输入.
当miner挖出一个新的区块时, 它会向新的区块添加一个coinbase交易. coinbase交易是一种特殊的交易, 它不需要引用之前一笔交易的输出. 它’凭空’产生了币(也就是产生了新币), 这是miner获得挖出mining的奖励, 也可以理解为’发行新币’.
在区块链的最初, 也就是第一个块, 叫做创世区块. 正是这个创世区块, 产生了区块链最开始的输出.对于创世区块, 不需要引用之前交易的输出.因为在创世区块之前根本不存在交易, 也就是不存在交易输出.
来创建一个coinbase交易:

func NewCoinbaseTX(to, data string) *Transaction {
	if data == "" {
		data = fmt.Sprintf("Reward to %s", to)
	}

	txin := TXInput{[]byte{}, -1, data}
	txout := TXOutput{subsidy, to}
	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
	tx.SetID()

	return &tx
}

coinbase交易只有一个输出, 没有输入. 在我们的实现中, 它表现为Txid为空, Vout等于-1.并且, 在当前实现中, coinbase交易也没有再ScriptSig中存储脚本, 而只是存储了一个任意的字符串data.

在比特币中, 第一笔coinbase交易包含了如下信息: “The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”。可点击这里查看

subsidy是挖出新区块的奖励金. 在比特币中, 实际并没有存储这个数字, 而是基于区块总数进行计算而得: 区块总数除以210000就是subsidy. 挖出创世区块的奖励是50BTC, 每挖出210000个区块后, 奖励减半. 在我们的实现中, 这个奖励值将会是一个常量(至少目前是).

将交易保存到区块链

从现在开始, 每个区块必须存储至少一笔交易. 如果没有交易, 也就不可能出新的块. 这意味着我们应该溢出Block的Data字段, 取而代之的是存储交易:

type Block struct {
	Timestamp     int64
	Transactions  []*Transaction
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}

NewBlock和NewGenesisBlock也必须做出相应的改变:

func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
	block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
	pow := NewProofOfWork(block)
	nonce, hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce

	return block
}

func NewGenesisBlock(coinbase *Transaction) *Block {
	return NewBlock([]*Transaction{coinbase}, []byte{})
}

接下来修改创建区块链的函数:

func CreateBlockchain(address string) *Blockchain {
	var tip []byte
	db, _ := bolt.Open(dbFile, 0600, nil)

	_ = db.Update(func(tx *bolt.Tx) error {
		cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
		genesis := NewGenesisBlock(cbtx)

		b, _ := tx.CreateBucket([]byte(blocksBucket))

		if b == nil {
			b, _ := tx.CreateBucket([]byte(blocksBucket))
			_ = b.Put(genesis.Hash, genesis.Serialize())
			_ = b.Put([]byte("l"), genesis.Hash)
			tip = genesis.Hash
		} else {
			tip = b.Get([]byte("l"))
		}
		return nil
	})
	bc := Blockchain{tip, db}

	return &bc
}

现在, 这个函数会接受一个地址作为参数, 这个地址将会被用来接收挖出创世区块的奖励.

工作量证明

工作量证明算法必须要将存储在区块里面的交易考虑进去, 从而保证区块链交易存储的一致性和可靠性. 所以, 我们必须修改ProofOfWork.prepareData方法:

func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.HashTransactions(), // This line was changed
			IntToHex(pow.block.Timestamp),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)

	return data
}

不像之前使用pow.block.Data, 现在我们使用pow.block.HashTransactions():

func (b *Block) HashTransactions() []byte {
	var txHashes [][]byte
	var txHash [32]byte

	for _, tx := range b.Transactions {
		txHashes = append(txHashes, tx.ID)
	}
	txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))
	return txHash[:]
}

通过哈希值提供数据的唯一表示, 这种做法我们已经不是第一次遇到了. 我们想要通过仅仅一个哈希值, 就可以识别一个块里面的所有交易. 为此, 先获得每笔交易的哈希值, 然后将它们关联起来, 最后获得一个连接后的组合哈希值.

比特币使用了一个更加复杂的技术: 它将一个区块里面包含的所有交易表示为一个Merkle tree, 然后在工作量证明系统中使用树的根哈希(root hash),这个方法能够让我们快速检索一个块里面是否包含了某笔交易, 即只需root hash而无需下载所有交易即可完成判断.

来检查目前为止是否正确:

╰─ ./blockchain_impl_in_go createblockchain -address Ivan                  ─╯ 
00000060b65eb7a78b68206835d12e06c8b00940da37b5c773c1d465a8a3a35f

Done!

很好, 我们已经获得了第一笔mining奖励, 但是, 我们要如何查看余额呢?

未花费的交易输出

我们需要找到所有的未花费交易输出(unspent transactions outputs, UTXO), 未花费(unspent)指的是这个输出还没有被包含在任何交易的输入中, 或者说没有被任何输入引用. 在上面图示中, 未花费的输出是
使用go语言构建区块链 Part4.事务1,Design Database,BlockChain,Golang,区块链,golang,开发语言

  1. tx0, output 1;
  2. tx1, output 0;
  3. tx3, output 0;
  4. tx4, output 0.

当然了, 检查余额时, 我们并不需要知道整个区块链上所哟䣌UTXO, 只需要关注那些我们能解锁的那些UTXO(目前我们还没有实现密钥, 所以我们将会使用用户定义的地址来代替). 首先, 让我们定义在输入和输出上的锁定和解锁方法:

func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
	return in.ScriptSig == unlockingData
}

func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
	return out.ScriptPubKey == unlockingData
}

在这里, 我们只是将script字段与unlockingData进行了比较. 在后续文章我们基于私钥实现了地址以后, 会对这部分进行改进.
下一步, 找到包含未花费输出的交易, 这一步其实相当困难:

func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
	var upsentTXs []Transaction
	spentTXOs := make(map[string][]int)
	bci := bc.Iterator()

	for {
		block := bci.Next()

		for _, tx := range block.Transactions {
			txID := hex.EncodeToString(tx.ID)
		Outputs:
			for outIdx, out := range tx.Vout {
				if spentTXOs[txID] != nil {
					for _, spentOut := range spentTXOs[txID] {
						if spentOut == outIdx {
							continue Outputs
						}
					}
				}
				if out.CanBeUnlockedWith(address) {
					upsentTXs = append(upsentTXs, *tx)
				}
			}
			if tx.IsCoinbase() == false {
				for _, in := range tx.Vin {
					if in.CanUnlockOutputWith(address) {
						inTxID := hex.EncodeToString(in.Txid)
						spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
					}
				}
			}
		}
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}

	return upsentTXs
}

由于交易被存储在区块里, 所以我们不得不检查区块链里的每一笔交易.从输出开始:

if out.CanBeUnlockedWith(address) {
	unspentTXs = append(unspentTXs, tx)
}

如果一个输出被一个地址锁定, 并且这个地址恰好是我们要找的地址, 那么这个输出就是我们想要的. 不过在获得它之前, 我们需要检查该输出是否已经被包含在一个交易的输出中, 也就是检查它是否已经被花费了:

if spentTXOs[txID] != nil {
	for _, spentOut := range spentTXOs[txID] {
		if spentOut == outIdx {
			continue Outputs
		}
	}
}

我们跳过那些已经被包含在其他输入中的输出(这说明这个输出已经被花费, 无法再使用了). 检查完输出以后, 我们将给定地址所有能够解锁输出的输入聚合起来(这并不适用于coinbase交易, 因为它们不解锁输出)

if tx.IsCoinbase() == false {
    for _, in := range tx.Vin {
        if in.CanUnlockOutputWith(address) {
            inTxID := hex.EncodeToString(in.Txid)
            spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
        }
    }
}

这个函数返回了一个交易列表, 里面包含了未花费输出.为了计算余额, 我们还需要一个函数将这些交易作为输入, 然后返回一个输出:

func (bc *Blockchain) FindUTXO(address string) []TXOutput {
       var UTXOs []TXOutput
       unspentTransactions := bc.FindUnspentTransactions(address)

       for _, tx := range unspentTransactions {
               for _, out := range tx.Vout {
                       if out.CanBeUnlockedWith(address) {
                               UTXOs = append(UTXOs, out)
                       }
               }
       }

       return UTXOs
}

就是这么多了!现在我们来实现getbalance命令

func (cli *CLI) getBalance(address string) {
	bc := NewBlockchain(address)
	defer bc.db.Close()

	balance := 0
	UTXOs := bc.FindUTXO(address)

	for _, out := range UTXOs {
		balance += out.Value
	}

	fmt.Printf("Balance of '%s': %d\n", address, balance)
}

账户余额就是由账户地址锁定的所有未花费交易输出的综合.
在挖出创世区块后, 来检查一下我们的余额:

╰─ ./blockchain_impl_in_go getbalance -address Ivan                        ─╯ 
Balance of Ivan: 10

这就是我们的第一笔钱!

发送币

现在, 我们想要给其他人发送一些币. 为此, 我们需要创建一笔新的交易, 将它放到一个区块里, 然后挖出这个区块. 之前我们只实现了coinbase交易(这是一种特殊的交易), 现在我们需要一种通用的普通交易.

func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	acc, validOutputs := bc.FindSpendableOutputs(from, amount)
	
	if acc < amount {
		log.Panic("ERROR: Not enough funds")
	}

	for txid, outs := range validOutputs {
		txID, _ := hex.DecodeString(txid)
		
		for _, out := range outs {
			input := TXInput{txID, out, from}
			inputs = append(inputs, input)
		}
	}
	outputs = append(outputs, TXOutput{amount, to})
	if acc >amount {
		outputs = append(outputs, TXOutput{acc - amount, from})
	}
	tx := Transaction{nil, inputs, outputs}
	tx.SetID()
	
	return &tx
}

在创建新的输出前, 我们首先必须找到所有的未花费输出, 并且确保它们有足够的价值(value),这就是FindSpendableOutputs方法要做的事情. 随后, 对于每个找到的输出, 会创建一个引用该输出的输入. 接下来, 我们创建两个输出:

  1. 一个由接收者地址锁定. 这是给其他地址实际转移的币
  2. 一个由发送者地址锁定.这是一个找零.只有当未花费输出超过新交易所需时产生. 记住: 输出是不可再分的.

FindSpendableOutputs方法基于之前定义的FindUnspentTransactions方法:

func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
	unspentOutputs := make(map[string][]int)
	unspentTXs := bc.FindUnspentTransactions(address)
	accumulated := 0
Work:
	for _, tx := range unspentTXs {
		txID := hex.EncodeToString(tx.ID)

		for outIdx, out := range tx.Vout {
			if out.CanBeUnlockedWith(address) && accumulated < amount {
				accumulated += out.Value
				unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

				if accumulated >= amount {
					break Work
				}
			}
		}
	}
	return accumulated, unspentOutputs
}

这个方法对所有未花费交易进行迭代, 并对它的值进行累加. 当累加值大于或等于我们想要传送的值时, 它就会停止并返回累加值, 同时返回的还有通过交易ID进行分组的输出索引. 我们只需取出足够支付的钱就够了.
现在我们可以修改Blockchain.MineBlock方法

func (bc *Blockchain) MineBlock(transactions []*Transaction) {
	var lastHash []byte

	_ = bc.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		lastHash = b.Get([]byte("l"))

		return nil
	})

	newBlock := NewBlock(transactions, lastHash)

	_ = bc.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		_ = b.Put(newBlock.Hash, newBlock.Serialize())
		_ = b.Put([]byte("l"), newBlock.Hash)
		bc.tip = newBlock.Hash

		return nil
	})
}

最后, 让我们实现send方法:

func (cli *CLI) send(from, to string, amount int) {
	bc := NewBlockchain(from)
	defer bc.db.Close()

	tx := NewUTXOTransaction(from, to, amount, bc)
	bc.MineBlock([]*Transaction{tx})
	fmt.Println("Success!")
}

发送币意味着创建新的交易, 并通过挖出新的区块的方式将交易打包到区块链中. 不过比特币并不是一连串立刻完成这些事情(虽然我们目前的实现时这么做的). 相反, 它会将所有新的交易放到一个内存池中(mempool),然后当miner准备挖出一个新区块时, 它从内存池中取出所有交易, 创建一个候选块. 只有当包含这些交易的区块被挖出来, 并添加到区块链以后, 里面的交易才开始确认.
让我们检查一下发送币是否能工作:

$ blockchain_go send -from Ivan -to Pedro -amount 6
000000655594c9b0c6c1034ec0236d91d2115bbd74ed008901ea81c29f231d7f

Success!

╰─ ./blockchain_impl_in_go getbalance -address Ivan                        ─╯ 
Balance of Ivan: 4

╰─ ./blockchain_impl_in_go getbalance -address Pedro                       ─╯ 
Balance of Pedro: 6

很好!现在, 让我们创建更多的交易, 确保从多个输出中发送币也正常工作:

╰─ ./blockchain_impl_in_go send -from Pedro -to Helen -amount 2            ─╯ 
0000003d3c12819d42b9c9a2968a803b651a775af1af262d51384d6c2577f8e1

Success!

╰─ ./blockchain_impl_in_go send -from Ivan -to Helen -amount 2             ─╯ 
00000050e41ef1982c2aab6ad43d748b7f65c720a34a6fb9f144960d911e4711

Success!

现在, Helen的币被锁定在了两个输出中: 一个来自Pedro, 一个来自Lvan.让我们把它们发送给其他人:

╰─ ./blockchain_impl_in_go send -from Helen -to Rachel -amount 3           ─╯ 
000000417eabcad236c9d42c5c33d72c5e63a293c2b5491390f362d1d3fcdb89

Success!

$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2

$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4

$ blockchain_go getbalance -address Helen
Balance of 'Helen': 1

$ blockchain_go getbalance -address Rachel
Balance of 'Rachel': 3

看起来没问题!现在, 来测试一些失败的情况:

$ blockchain_go send -from Pedro -to Ivan -amount 5
panic: ERROR: Not enough funds

$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4

$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2

总结

虽然不容易, 但是现在终于实现交易了!不过, 我们依然缺少了一些像比特币那样的一些关键特性:

  1. 地址(address). 我们现在还没有基于私钥(private key)的真实地址.
  2. 奖励(reward). 现在mining时肯定无法盈利的!
  3. UTXO集. 获取余额需要扫描整个区块链, 而当区块非常多时, 这么做就会花费很长时间. 并且, 如果我们想要验证后续交易, 也需要花费很长时间. 而UTXO集就是为了解决这些问题, 加快交易相关的操作.
  4. 内存池(mempool). 在交易被打包到区块之前, 这些交易被存储在内存池里面. 在我们目前的实现中,一个块仅仅包含一笔交易, 这是相当低效的.

link

Full source codes
Transaction
Merkle tree
Coinbase文章来源地址https://www.toymoban.com/news/detail-738421.html

到了这里,关于使用go语言构建区块链 Part4.事务1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# GDAL 数字图像处理Part4 获得鼠标位置的地理坐标

            其实在程序设计中,很大部分的工作量都在搞懂Winform的各种控件及其接口,网上的讲解质量也良莠不齐,所以如何使用控件也是一个很苦恼的问题(舍友深受困扰:怎么没有系统性讲Winform的资料)。我也在考虑要不要写一下文章讲讲comboBox、listView、tabControl、pictureBo

    2024年02月10日
    浏览(42)
  • 【果树农药喷洒机器人】Part4:果树冠层图像实例分割模型优化

    📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍 收藏 ⭐不迷路🙉 📢:内容若有错误,敬请留言 📝指正!原创文,转载请注明出处 为准确获取柑橘树

    2024年02月13日
    浏览(40)
  • [Machine learning][Part4] 多维矩阵下的梯度下降线性预测模型的实现

    目录 模型初始化信息: 模型实现: 多变量损失函数: 多变量梯度下降实现: 多变量梯度实现: 多变量梯度下降实现: 之前部分实现的梯度下降线性预测模型中的training example只有一个特征属性:房屋面积,这显然是不符合实际情况的,这里增加特征属性的数量再实现一次

    2024年02月06日
    浏览(45)
  • 从零实现一个数据库(DataBase) Go语言实现版 0.介绍

    英文源地址 我们为什么需要数据库?为什么不是直接把数据dump进文件中. 第一个话题就是持久化. 我们将讨论如果写入文件的过程中程序崩溃了, 或者电源断电了, 文件的状态会是什么样的呢? 文件是否只是丢失了最后一次写操作? 或者以写了一半的文件结束 或者是以更差的状态

    2024年02月06日
    浏览(40)
  • [足式机器人]Part4 机械设计 Ch00/01 绪论+机器结构组成与连接 ——【课程笔记】

    本文仅供学习使用 本文参考: 《机械设计》 王德伦 马雅丽 课件与日常作业可登录网址 http://edu.bell-lab.com/manage/#/login ,选择观摩登录,查看 2023机械设计2 。 机械设计 Machines Design ,在传统课程中,更倾向于 机械零件设计 Machine Elements Design :预期 装置 (运动/结构)与 性能

    2024年02月13日
    浏览(54)
  • [足式机器人]Part4 南科大高等机器人控制课 CH12 Robotic Motion Control

    本文仅供学习使用 本文参考: B站:CLEAR_LAB 笔者带更新-运动学 课程主讲教师: Prof. Wei Zhang 课程链接 : https://www.wzhanglab.site/teaching/mee-5114-advanced-control-for-robotics/ 机器人—— 运动能力 、计算能力、感知决策能力 的机电系统 Steady-state error : e s s = lim ⁡ t → ∞ θ e ( t ) e_{m

    2024年02月03日
    浏览(46)
  • [足式机器人]Part4 南科大高等机器人控制课 CH11 Bascis of Optimization

    本文仅供学习使用 本文参考: B站:CLEAR_LAB 笔者带更新-运动学 课程主讲教师: Prof. Wei Zhang 课程链接 : https://www.wzhanglab.site/teaching/mee-5114-advanced-control-for-robotics/ Optimization is argulably the most important tool for modern engineering Robotics: Differential Inverse Kinematics Dynamics : ABA (most efficient

    2024年02月04日
    浏览(65)
  • [足式机器人]Part4 南科大高等机器人控制课 CH10 Bascis of Stability Analysis

    本文仅供学习使用 本文参考: B站:CLEAR_LAB 笔者带更新-运动学 课程主讲教师: Prof. Wei Zhang This lecture introduces basic concepts and results on Lyapunov stability of nonlinear systems system asymptotic/ˌæsimp\\\'tɔtik,-kəl/ 渐进的 behavior (not too much about transient/\\\'trænzɪənt/短暂的 ) ability to return to the des

    2024年02月04日
    浏览(47)
  • [足式机器人]Part4 南科大高等机器人控制课 Ch09 Dynamics of Open Chains

    本文仅供学习使用 本文参考: B站:CLEAR_LAB 笔者带更新-运动学 课程主讲教师: Prof. Wei Zhang Recall Newton-Euler Equation for a single rigid body: F = d d t H = I A + V ~ ∗ I V mathcal{F} =frac{mathrm{d}}{mathrm{d}t}mathcal{H} =mathcal{I} mathcal{A} +tilde{mathcal{V}}^*mathcal{I} mathcal{V} F = d t d ​ H = I A + V

    2024年02月03日
    浏览(60)
  • [足式机器人]Part4 南科大高等机器人控制课 Ch05 Instantaneous Velocity of Moving Frames

    本文仅供学习使用 本文参考: B站:CLEAR_LAB 笔者带更新-运动学 课程主讲教师: Prof. Wei Zhang Given Frame trajectory [ T ] ( t ) = ( [ Q ] ( t ) , R ⃗ p ( t ) ) left[ T right] left( t right) =left( left[ Q right] left( t right) ,vec{R}_pleft( t right) right) [ T ] ( t ) = ( [ Q ] ( t ) , R p ​ ( t ) ) wrt { O } l

    2024年02月05日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包