笔记:机器学习入门专栏笔记对应jupyternotebook以及封装的各种算法个人笔记,如有错误,感谢指出!-机器学习文档类资源-CSDN文库
三,最基础的分类算法-k邻近算法 KNN
概括:
根据经验取k值,找与k值最接近的k个点,进而判断预测数据的种类
一,KNN程序试写
KNN基础
首先类似上一节的鸢尾花实例,绘画出自己创建的数据的点。
再添加一个待评价的点,用蓝色表示:
x =np.array([8.093607318, 3.365731514])
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1], color='g')
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1], color='r')
plt.scatter(x[0], x[1],color='b')
plt.show()
KNN过程
欧拉距离就是俩个点在N维空间的距离
from math import sqrt
distances =[sqrt(np.sum((x_train - x)**2))
for x_train in X_train]
np.argsort(distances)#从左到右是距离从近到远点的索引
nearest =np.argsort(distances)
k = 6 #取一个k邻近的经验值
topK_y =[y_train[neighbor] for neighbor in nearest[:k]]
#得到的是k个邻近点的标签
topK_y=[1, 1, 1, 1, 1, 0]
from collections importCounter
votes = Counter(topK_y)#对数组中元素的频次做一个统计
Votes=Counter({1: 5, 0: 1}) #可以理解为一个字典
votes.most_common(1)=[(1, 5)] #取票数最多的1个元素
predict_y =votes.most_common(1)[0][0]
predict_y=1
机器学习的过程,fit是拟合的过程,predict是预测过程
KNN算法并没有模型,是所有算法中唯一一个没有模型的算法,训练数据本身就是他的模型。
二,使用scikit-learn中的KNN
sklearn已经封装好了,需要做的就是调包,并把训练数据和预测数据进行处理,并给相关参数赋值之后代入就可以了。
from sklearn.neighborsimport KNeighborsClassifier
kNN_classifier =KNeighborsClassifier(n_neighbors=6)
kNN_classifier.fit(X_train,y_train) #对于每个sklearn里的算法,都需要先进行拟合,在这里传入俩个参数,分别是数据集和标签的向量
X_predict= x.reshape(1, -1) #必须把要预测的数据转化为矩阵形式
X_predict=array([[8.09360732, 3.36573151]])#由数组变成矩阵
#一对[]是数组,想要变成矩阵还需要一对[],数组可以是矩阵的行向量
kNN_classifier.predict(X_predict)
#返回array([1])
y_predict =kNN_classifier.predict(X_predict)
y_predict[0]
#返回1
尝试自己仿写KNeighborsClassifier:
KNN:
在 fit训练模型中,发现KNN的模型就是数据本身!其他算法这里会很复杂
三,判断机器学习性能
训练的模型直接在真实环境中使用
问题:
-模型很差会带来真实损失
-真实环境难以拿到真实的label
仿写train_test_split
导入鸢尾花的数据集:
import numpy as np
importmatplotlib.pyplot as plt
from sklearn importdatasets
iris =datasets.load_iris()
iris.keys()
X = iris.data
y = iris.target
训练数据集于测试数据集的切分:train test split
首先要对数据集进行随机化处理,再切分
shuffled_indexes = np.random.permutation(len(X))#生成数个随机的索引
test_ratio = 0.2#测试数据集的比例
test_size= int(len(X) * test_ratio) #注意进行强制类型转换,防止产生浮点型
test_indexes =shuffled_indexes[:test_size]
train_indexes =shuffled_indexes[test_size:]
X_train =X[train_indexes]
y_train =y[train_indexes]
X_test =X[test_indexes]
y_test =y[test_indexes]
returnX_train, X_test, Y_train, Y_test #注意一定要返回这些值,否则无意义
封装好后测试:
a是预测准确率
在这里,python自带的sum函数作用于布尔类型会有警告,但使用numpy的sum函数不会有警告,并且numpy的sum函数更快。
对函数的理解:函数里定义的是形参,只是相当于字符,是空的,只有当在测试时候给形参赋值才变得有意义。并且,如果定义的函数里有未引用的变量,在最后一定要返回这些变量,否则变量无效。
sklearn中的train_test_split
sklearn已经有了上边封装好的函数,可以直接调用
fromsklearn.model_selection import train_test_split
X_train,X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state=666) #test_size默认值也是0.2,后边的是随机种子
四,分类准确度
import numpy as np
importmatplotlib.pyplot as plt
fromsklearn import datasets #手写识别数据库,格式相当于一个字典
digits =datasets.load_digits()
数据库信息:
X = digits.data
y = digits.target
some_digit = X[666]
some_digit_image =some_digit.reshape(8, 8)
import matplotlib
importmatplotlib.pyplot as plt
plt.imshow(some_digit_image, cmap =matplotlib.cm.binary)
plt.show()
之后测试同上,整体测试为:
skikit-learn中的accuracy_score
from sklearn.metricsimport accuracy_score
accuracy_score(y_test,y_predict)
可直接调用
五,超参数
超参数:在运行机器学习算法之前需要决定的参数,比如KNN算法中的k
模型参数:算法过程中学习的参数
算法工程师要调参,就是超参数
寻找好的超参数需要设计到不用领域的知识,经验数值或者实验搜索
不考虑距离:
寻找最好的的K,只需比较不同k值下的准确度就可以,利用for循环
#使用手写数字识别的数据库
best_score = 0.0
best_k = -1
for k in range(1, 11):
knn_clf =KNeighborsClassifier(n_neighbors=k)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_score = score
考虑距离:
但是这个过程没有考虑距离得的权重
虽然距离绿点最近的蓝点有俩个,红点只有一个,不考虑距离我们可以认为绿色应该属于蓝色,但是考虑距离,绿点距离红色最近,需要优化。
在这里我们可以使用距离的倒数作为权重来优化这个算法,并且可以解决平票的问题。
sklearn的knn算法中含有权重的参数,寻找过程只需要再嵌套一个循环
best_score = 0.0
best_k = -1
best_method =""
for method in["uniform", "distance"]:
for k in range(1, 11):
knn_clf =KNeighborsClassifier(n_neighbors=k, weights=method)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_score = score
best_method = method
距离怎么定义?
曼哈顿距离是俩点横纵坐标差的绝对值的和
明可夫斯基距离类似欧拉距离:
因此考虑超参数p时:
best_score = 0.0
best_k = -1
best_p = -1
for k in range(1, 11):
for p in range(1, 6):
knn_clf =KNeighborsClassifier(n_neighbors=k, weights="distance", p=p)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_p = p
best_score = score
上述对超参数的搜索过程也可以称为:网格搜索
六,网格搜索于KNN算法更多的超参数
使用sklearn自带的GridSearch函数,首先要定义para_grid参数,即要搜索的参数集合
param_grid = [
{
'weights': ['uniform'],
'n_neighbors': [i for i in range(1,11)]
}, #这是第一个参数,参数包含俩个字典
{
'weights': ['distance'],
'n_neighbors': [i for i in range(1,11)],
'p': [i for i in range(1, 6)]
} #这是第二个参数,参数包含三个字典
]
是一个数组,数组中每一个参数是一个字典,字典的键对应参数的名称,字典的值是个列表,该列表存储键对应的所有值的范围。第一个参数中定义权重只是uniform,不对p参数进行搜索,第二个参数定义权重与距离相关,对p参数进行搜索。
knn_clf= KNeighborsClassifier() #创建默认的KNN参数,无需传递任何参数
fromsklearn.model_selection import GridSearchCV #CV后续介绍
grid_search= GridSearchCV(knn_clf, param_grid) #创建GridSearchCV对应的实例对象,括号里第一个参数表示对哪个分类器进行网格搜索,第二个参数表示网格搜索对应的参数。
grid_search.fit(X_train,y_train) #用创建的实例对象来训练数据
grid_search.best_estimator_ #返回最佳分类器对应的参数
返回值和上一小节用的网格搜索得出的结果不一样,原因是GridSearchCV中CV代表交叉验证,是一种更加复杂的验证方式
grid_search.best_score_ #得到最佳准确率
grid_search= GridSearchCV(knn_clf, param_grid, n_jobs=-1, verbose=2) #n_jobs表示使用计算机的核进行多线程操作,-1表示运用所有的核
更多的距离的定义
metrics: http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html
七,数据归一化Feature Scaling
量纲不同,距离的计算很容易被数值比较大的量纲主导,因此需要归一化量纲
解决方案:把所有的数据映射到同一尺度
最值归一化:把所有的数据映射到0-1之间
适用于分布又明显边界的情况,受outlier(过于突出的数据)影响较大(比如分数或像素点),但是对于收入等问题不适用。
X =np.random.randint(0, 100, (50, 2))
X = np.array(X,dtype=float)
X =np.array(X, dtype=float) #对元素进行强制类型转换,变成浮点数,方便归一化的计算
X[:,0] = (X[:,0] -np.min(X[:,0])) / (np.max(X[:,0]) - np.min(X[:,0]))
X[:,1] = (X[:,1] -np.min(X[:,1])) / (np.max(X[:,1]) - np.min(X[:,1]))
plt.scatter(X[:,0],X[:,1])
plt.show()
改进:均值方差归一化standardization:
把所有数据归一到均值为0,方差为1的分布中。适用于数据分布没有明显边界,有可能存在极端数据值
X2 =np.random.randint(0, 100, (50, 2))
X2 = np.array(X2,dtype=float)
X2[:,0] = (X2[:,0] -np.mean(X2[:,0])) / np.std(X2[:,0])
X2[:,1] = (X2[:,1] -np.mean(X2[:,1])) / np.std(X2[:,1])
plt.scatter(X2[:,0],X2[:,1])
plt.show()
sklearn中的Scaler归一化
对测试数据集进行归一化?
当我们对训练数据集首先进行均值方差归一化,之后进行训练后得到了一个模型,再然后我们可以将测试数据集再次进行均值方差归一化后直接送给模型进行测试吗?(概率统计的角度?)
答:不可以,训练数据集和测试数据集的均值方差归一化必须是按照相同的均值和方差进行归一化的,也就是说,测试数据集的归一化需要用训练数据集得到的均值和方差的参数的结果进行归一化。(X_test-mean_train)/std_train
原因是:测试数据集是模拟真实环境,真实环境很有可能无法得到所有测试数据的均值和方差,对于新来的需要测试的数据集,我们要使用已经训练好模型的均值和方差进行测试,而不是另起炉灶,这样剥离了训练数据集和测试数据集的关系。对数据的归一化也是算法的一部分!
因此,我们需要保存训练数据集得到的均值和方差
sklearn已经封装好了归一化的函数Scalar
其实就是把之前的predict变成了transform
iris =datasets.load_iris()
X = iris.data
y = iris.target
fromsklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2,random_state=666)
标准归一化:
fromsklearn.preprocessing import StandardScaler #数据的归一化在数据的预处理模块
standardScalar =StandardScaler()
standardScalar.fit(X_train)
standardScalar.mean_ #一种命名规则,对于通过用户输入的数据计算出来的变量,并且这些变量可以让用户在程序外随时查看的,一般要在命名后加下划线
standardScalar.scale_ #描述数据分布范围的量,方差只是其中一种统计指标
X_train=standardScalar.transform(X_train)
X_test_standard =standardScalar.transform(X_test)
之后就可以使用归一化后的数进行KNN分类了
八,更多有关K近邻算法
天然可以解决多分类问题,思想简单,效果强大
使用k近邻可以解决回归问题,sklearn封装好了KNeighborsRegressor解决这一问题。可参考官方文档。
缺点是效率低下,如果数据有m个样本,n个特征,则预测每一个新的数据,需要O(m*n),优化可以使用树的方式,但依旧效率低下
缺点2是高度数据相关,对突出值(错误)非常敏感
缺点3是结果不具有可解释性
最大缺点:维数灾难(随着维数的增加,看似相近的俩个点之间的距离越来越大)解决方法是降维
机器学习流程回顾:
三,最基础的分类算法-k邻近算法 KNN
概括:
根据经验取k值,找与k值最接近的k个点,进而判断预测数据的种类
一,KNN程序试写
KNN基础
首先类似上一节的鸢尾花实例,绘画出自己创建的数据的点。
再添加一个待评价的点,用蓝色表示:
x =np.array([8.093607318, 3.365731514])
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1], color='g')
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1], color='r')
plt.scatter(x[0], x[1],color='b')
plt.show()
KNN过程
欧拉距离就是俩个点在N维空间的距离
from math import sqrt
distances =[sqrt(np.sum((x_train - x)**2))
for x_train in X_train]
np.argsort(distances)#从左到右是距离从近到远点的索引
nearest =np.argsort(distances)
k = 6 #取一个k邻近的经验值
topK_y =[y_train[neighbor] for neighbor in nearest[:k]]
#得到的是k个邻近点的标签
topK_y=[1, 1, 1, 1, 1, 0]
from collections importCounter
votes = Counter(topK_y)#对数组中元素的频次做一个统计
Votes=Counter({1: 5, 0: 1}) #可以理解为一个字典
votes.most_common(1)=[(1, 5)] #取票数最多的1个元素
predict_y =votes.most_common(1)[0][0]
predict_y=1
机器学习的过程,fit是拟合的过程,predict是预测过程
KNN算法并没有模型,是所有算法中唯一一个没有模型的算法,训练数据本身就是他的模型。
二,使用scikit-learn中的KNN
sklearn已经封装好了,需要做的就是调包,并把训练数据和预测数据进行处理,并给相关参数赋值之后代入就可以了。
from sklearn.neighborsimport KNeighborsClassifier
kNN_classifier =KNeighborsClassifier(n_neighbors=6)
kNN_classifier.fit(X_train,y_train) #对于每个sklearn里的算法,都需要先进行拟合,在这里传入俩个参数,分别是数据集和标签的向量
X_predict= x.reshape(1, -1) #必须把要预测的数据转化为矩阵形式
X_predict=array([[8.09360732, 3.36573151]])#由数组变成矩阵
#一对[]是数组,想要变成矩阵还需要一对[],数组可以是矩阵的行向量
kNN_classifier.predict(X_predict)
#返回array([1])
y_predict =kNN_classifier.predict(X_predict)
y_predict[0]
#返回1
尝试自己仿写KNeighborsClassifier:
KNN:
在 fit训练模型中,发现KNN的模型就是数据本身!其他算法这里会很复杂
三,判断机器学习性能
训练的模型直接在真实环境中使用
问题:
-模型很差会带来真实损失
-真实环境难以拿到真实的label
仿写train_test_split
导入鸢尾花的数据集:
import numpy as np
importmatplotlib.pyplot as plt
from sklearn importdatasets
iris =datasets.load_iris()
iris.keys()
X = iris.data
y = iris.target
训练数据集于测试数据集的切分:train test split
首先要对数据集进行随机化处理,再切分
shuffled_indexes = np.random.permutation(len(X))#生成数个随机的索引
test_ratio = 0.2#测试数据集的比例
test_size= int(len(X) * test_ratio) #注意进行强制类型转换,防止产生浮点型
test_indexes =shuffled_indexes[:test_size]
train_indexes =shuffled_indexes[test_size:]
X_train =X[train_indexes]
y_train =y[train_indexes]
X_test =X[test_indexes]
y_test =y[test_indexes]
returnX_train, X_test, Y_train, Y_test #注意一定要返回这些值,否则无意义
封装好后测试:
a是预测准确率
在这里,python自带的sum函数作用于布尔类型会有警告,但使用numpy的sum函数不会有警告,并且numpy的sum函数更快。
对函数的理解:函数里定义的是形参,只是相当于字符,是空的,只有当在测试时候给形参赋值才变得有意义。并且,如果定义的函数里有未引用的变量,在最后一定要返回这些变量,否则变量无效。
sklearn中的train_test_split
sklearn已经有了上边封装好的函数,可以直接调用
fromsklearn.model_selection import train_test_split
X_train,X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state=666) #test_size默认值也是0.2,后边的是随机种子
四,分类准确度
import numpy as np
importmatplotlib.pyplot as plt
fromsklearn import datasets #手写识别数据库,格式相当于一个字典
digits =datasets.load_digits()
数据库信息:
X = digits.data
y = digits.target
some_digit = X[666]
some_digit_image =some_digit.reshape(8, 8)
import matplotlib
importmatplotlib.pyplot as plt
plt.imshow(some_digit_image, cmap =matplotlib.cm.binary)
plt.show()
之后测试同上,整体测试为:
skikit-learn中的accuracy_score
from sklearn.metricsimport accuracy_score
accuracy_score(y_test,y_predict)
可直接调用
五,超参数
超参数:在运行机器学习算法之前需要决定的参数,比如KNN算法中的k
模型参数:算法过程中学习的参数
算法工程师要调参,就是超参数
寻找好的超参数需要设计到不用领域的知识,经验数值或者实验搜索
不考虑距离:
寻找最好的的K,只需比较不同k值下的准确度就可以,利用for循环
#使用手写数字识别的数据库
best_score = 0.0
best_k = -1
for k in range(1, 11):
knn_clf =KNeighborsClassifier(n_neighbors=k)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_score = score
考虑距离:
但是这个过程没有考虑距离得的权重
虽然距离绿点最近的蓝点有俩个,红点只有一个,不考虑距离我们可以认为绿色应该属于蓝色,但是考虑距离,绿点距离红色最近,需要优化。
在这里我们可以使用距离的倒数作为权重来优化这个算法,并且可以解决平票的问题。
sklearn的knn算法中含有权重的参数,寻找过程只需要再嵌套一个循环
best_score = 0.0
best_k = -1
best_method =""
for method in["uniform", "distance"]:
for k in range(1, 11):
knn_clf =KNeighborsClassifier(n_neighbors=k, weights=method)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_score = score
best_method = method
距离怎么定义?
曼哈顿距离是俩点横纵坐标差的绝对值的和
明可夫斯基距离类似欧拉距离:
因此考虑超参数p时:
best_score = 0.0
best_k = -1
best_p = -1
for k in range(1, 11):
for p in range(1, 6):
knn_clf =KNeighborsClassifier(n_neighbors=k, weights="distance", p=p)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k = k
best_p = p
best_score = score
上述对超参数的搜索过程也可以称为:网格搜索
六,网格搜索于KNN算法更多的超参数
使用sklearn自带的GridSearch函数,首先要定义para_grid参数,即要搜索的参数集合
param_grid = [
{
'weights': ['uniform'],
'n_neighbors': [i for i in range(1,11)]
}, #这是第一个参数,参数包含俩个字典
{
'weights': ['distance'],
'n_neighbors': [i for i in range(1,11)],
'p': [i for i in range(1, 6)]
} #这是第二个参数,参数包含三个字典
]
是一个数组,数组中每一个参数是一个字典,字典的键对应参数的名称,字典的值是个列表,该列表存储键对应的所有值的范围。第一个参数中定义权重只是uniform,不对p参数进行搜索,第二个参数定义权重与距离相关,对p参数进行搜索。
knn_clf= KNeighborsClassifier() #创建默认的KNN参数,无需传递任何参数
fromsklearn.model_selection import GridSearchCV #CV后续介绍
grid_search= GridSearchCV(knn_clf, param_grid) #创建GridSearchCV对应的实例对象,括号里第一个参数表示对哪个分类器进行网格搜索,第二个参数表示网格搜索对应的参数。
grid_search.fit(X_train,y_train) #用创建的实例对象来训练数据
grid_search.best_estimator_ #返回最佳分类器对应的参数
返回值和上一小节用的网格搜索得出的结果不一样,原因是GridSearchCV中CV代表交叉验证,是一种更加复杂的验证方式
grid_search.best_score_ #得到最佳准确率
grid_search= GridSearchCV(knn_clf, param_grid, n_jobs=-1, verbose=2) #n_jobs表示使用计算机的核进行多线程操作,-1表示运用所有的核
更多的距离的定义
metrics: http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html
七,数据归一化Feature Scaling
量纲不同,距离的计算很容易被数值比较大的量纲主导,因此需要归一化量纲
解决方案:把所有的数据映射到同一尺度
最值归一化:把所有的数据映射到0-1之间
适用于分布又明显边界的情况,受outlier(过于突出的数据)影响较大(比如分数或像素点),但是对于收入等问题不适用。
X =np.random.randint(0, 100, (50, 2))
X = np.array(X,dtype=float)
X =np.array(X, dtype=float) #对元素进行强制类型转换,变成浮点数,方便归一化的计算
X[:,0] = (X[:,0] -np.min(X[:,0])) / (np.max(X[:,0]) - np.min(X[:,0]))
X[:,1] = (X[:,1] -np.min(X[:,1])) / (np.max(X[:,1]) - np.min(X[:,1]))
plt.scatter(X[:,0],X[:,1])
plt.show()
改进:均值方差归一化standardization:
把所有数据归一到均值为0,方差为1的分布中。适用于数据分布没有明显边界,有可能存在极端数据值
X2 =np.random.randint(0, 100, (50, 2))
X2 = np.array(X2,dtype=float)
X2[:,0] = (X2[:,0] -np.mean(X2[:,0])) / np.std(X2[:,0])
X2[:,1] = (X2[:,1] -np.mean(X2[:,1])) / np.std(X2[:,1])
plt.scatter(X2[:,0],X2[:,1])
plt.show()
sklearn中的Scaler归一化
对测试数据集进行归一化?
当我们对训练数据集首先进行均值方差归一化,之后进行训练后得到了一个模型,再然后我们可以将测试数据集再次进行均值方差归一化后直接送给模型进行测试吗?(概率统计的角度?)
答:不可以,训练数据集和测试数据集的均值方差归一化必须是按照相同的均值和方差进行归一化的,也就是说,测试数据集的归一化需要用训练数据集得到的均值和方差的参数的结果进行归一化。(X_test-mean_train)/std_train
原因是:测试数据集是模拟真实环境,真实环境很有可能无法得到所有测试数据的均值和方差,对于新来的需要测试的数据集,我们要使用已经训练好模型的均值和方差进行测试,而不是另起炉灶,这样剥离了训练数据集和测试数据集的关系。对数据的归一化也是算法的一部分!
因此,我们需要保存训练数据集得到的均值和方差
sklearn已经封装好了归一化的函数Scalar
其实就是把之前的predict变成了transform
iris =datasets.load_iris()
X = iris.data
y = iris.target
fromsklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2,random_state=666)
标准归一化:
fromsklearn.preprocessing import StandardScaler #数据的归一化在数据的预处理模块
standardScalar =StandardScaler()
standardScalar.fit(X_train)
standardScalar.mean_ #一种命名规则,对于通过用户输入的数据计算出来的变量,并且这些变量可以让用户在程序外随时查看的,一般要在命名后加下划线
standardScalar.scale_ #描述数据分布范围的量,方差只是其中一种统计指标
X_train=standardScalar.transform(X_train)
X_test_standard =standardScalar.transform(X_test)
之后就可以使用归一化后的数进行KNN分类了
八,更多有关K近邻算法
天然可以解决多分类问题,思想简单,效果强大
使用k近邻可以解决回归问题,sklearn封装好了KNeighborsRegressor解决这一问题。可参考官方文档。
缺点是效率低下,如果数据有m个样本,n个特征,则预测每一个新的数据,需要O(m*n),优化可以使用树的方式,但依旧效率低下
缺点2是高度数据相关,对突出值(错误)非常敏感
缺点3是结果不具有可解释性
最大缺点:维数灾难(随着维数的增加,看似相近的俩个点之间的距离越来越大)解决方法是降维