今天我写一篇关于查重算法的例子,查重算法一般在网上资源比较少,如果搜索的话我建议搜索关键字“查重算法+空间向量+余弦定理”;为啥这么搜索呢,接下来我先讲一下空间向量和余弦定理跟查重算法的关系: 原文地址:
今天我写一篇关于查重算法的例子,查重算法一般在网上资源比较少,如果搜索的话我建议搜索关键字“查重算法+空间向量+余弦定理”;为啥这么搜索呢,接下来我先讲一下空间向量和余弦定理跟查重算法的关系:
原文地址:
相信很多学习向量空间模型(Vector Space Model)的人都会被其中的余弦定理公式所迷惑

因为一看到余弦定理,肯定会先想起初中时的那条最简单的公式cosA=a/c(邻边比斜边),见下图:

但是,初中那条公式是只适用于直角三角形的,而在非直角三角形中,余弦定理的公式是:
cosA=(c2+ b2- a2)/2bc
不过这条公式也和向量空间模型中的余弦定理公式不沾边,迷惑..
-------------------引用开始分界线------------------------
假定三角形的三条边为 a, b 和 c,对应的三个角为 A, B 和 C,那么角 A 的余弦

如果我们将三角形的两边 b 和 c 看成是两个向量,那么上述公式等价于

其中分母表示两个向量 b 和 c 的长度,分子表示两个向量的内积。
举一个具体的例子,假如新闻 X 和新闻 Y 对应向量分别是x1,x2,...,x64000 和y1,y2,...,y64000,
那么它们夹角的余弦等于

-------------------引用完毕分界线------------------------
高中那条公式又怎么会等价于向量那条公式呢?
原来它从高中的平面几何跳跃到大学的线性代数的向量计算..
在线性代数的向量计算的余弦定理中,
* 分子是两个向量的点积(wiki),点积的定理和计算公式:
The dot product of two vectors a = [a1, a2, … , an] and b = [b1, b2, … , bn] is defined as:

点积(dot product),又叫内积,数量积..(Clotho注: product常见的是产品的意思,但在数学上是乘积的意思.)
* 分母是两个向量的长度相乘.这里的向量长度的计算公式也比较难理解.
假设是二维向量或者三维向量,可以抽象地理解为在直角坐标轴中的有向线段,如图:

d2 = x2 + y2 -> d = sprt(x2 + y2)

d2 = x2 + y2 + z2 -> d = sprt(x2 + y2 + z2)
三维以上的维度很难用图来表示,但是再多维度的向量,也仍然可以用这条公式来计算:
dn2 = x12 + x22 + .. + xn2 -> dn = sprt(x12 + x22 + .. + xn2)
在文本相似度计算中,向量中的维度x1,x2..xn其实就是词项(term)的权重,一般就是词项的tf-idf值.
而这条看上去很抽象的公式,其实就是为了计算两篇文章的相似度.

文本相似度计算的处理流程是:
1.对所有文章进行分词
2.分词的同时计算各个词的tf值
3.所有文章分词完毕后计算idf值
4.生成每篇文章对应的n维向量(n是切分出来的词数,向量的项就是各个词的tf-idf值)
5.对文章的向量两篇两篇代入余弦定理公式计算,得出的cos值就是它们之间的相似度了
当两条新闻向量夹角的余弦等于一时,这两条新闻完全重复(用这个办法可以删除重复的网页);当夹角的余弦接近于一时,两条新闻相似,从而可以归成一类;夹角的余弦越小,两条新闻越不相关。
余弦定理是常见的相似度衡量方法之一,
7. 夹角余弦(Cosine)
有没有搞错,又不是学几何,怎么扯到夹角余弦了?各位看官稍安勿躁。几何中夹角余弦可用来衡量两个向量方向的差异,机器学习中借用这一概念来衡量样本向量之间的差异。
(1)在二维空间中向量A(x1,y1)与向量B(x2,y2)的夹角余弦公式:

(2) 两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n)的夹角余弦
类似的,对于两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n),可以使用类似于夹角余弦的概念来衡量它们间的相似程度。

即:

夹角余弦取值范围为[-1,1]。夹角余弦越大表示两个向量的夹角越小,夹角余弦越小表示两向量的夹角越大。当两个向量的方向重合时夹角余弦取最大值1,当两个向量的方向完全相反夹角余弦取最小值-1。
原文地址:
下面我将就这自己的项目进行讲解,而且我的项目中由于业务需求,需要对试题进行查重,这样的话一般的科目没问题但是我们公司数学里面的公式,都是图片格式存储的,在用这个查重就不行了。我又自己创新了一个
图片查重程序,下面我将进行讲解:


public class Program
{
static void Main(string[] args)
{
FindDuplicate(68);
}
public static List<int> FindDuplicate(int id)
{
ModelOPE.Controller controller = new ModelOPE.Controller();
string sql = "select ID,BankID,QuesBody from " + Model.QuesModel.TableName + " where ID=@id";
ModelOPE.SqlParams parameters = new ModelOPE.SqlParams();
parameters.Add("@id", id);
List<Model.QuesModel> modelList = new List<QuesModel>();
modelList = controller.Query<Model.QuesModel>(sql, parameters.toArray());
if (modelList.Count == 0)
Library.Debug("ID=" + id.ToString() + "的试题不存在。");
Model.QuesModel Ques = modelList[0];
int bankid = Ques.BankID;
List<int> idList = new List<int>();
string text1 = string.Empty;
string text2 = string.Empty;
TextCompare compare;
text1 = Library.RemoveHtml(Ques.QuesBody);
compare = new TextCompare(text1, "");
string freqWord = compare.GetHighFreqWord();
modelList = HubbleQues.QuesSearch(freqWord, bankid, 0, 0, "", "", 1, 5, true, false);
//string msg = "提取的关键字【" + freqWord + "】";
//msg += "雷同试题个数" + modelList.Count + ";";
//System.Web.HttpContext.Current.Response.Write(msg);
//if (modelList.Count > 0)
//{
// foreach (Model.QuesModel ques in modelList) msg += "," + ques.ID;
//}
foreach (Model.QuesModel model in modelList)
{
text2 = Library.RemoveHtml(model.QuesBody);
compare = new TextCompare(text1, text2);
double similar = compare.GetSimilar();
if (similar > 0.9) idList.Add(model.ID);
}
//if (idList.Count > 0)
//{
// foreach (int idL in idList)
// {
// msg += ",雷同试题ID=" + idL;
// }
//}
//System.Web.HttpContext.Current.Response.Write(msg);
return idList;
}
}
/// <summary>
/// 文本相似度计算及提取高频关键字类
/// </summary>
public class TextCompare
{
private string TextStr1;
private string TextStr2;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="RefStr1"></param>
/// <param name="RefStr2"></param>
public TextCompare(string RefStr1, string RefStr2)
{
TextStr1 = RefStr1;
TextStr2 = RefStr2;
}
/// <summary>
/// 获取相似度
/// </summary>
/// <returns></returns>
public double GetSimilar()
{
string UniteQuesText = TextStr1 + TextStr2;
string TextVector1 = GetTextVector(TextStr1, UniteQuesText);
string TextVector2 = GetTextVector(TextStr2, UniteQuesText);
return CalculateVectorCos(TextVector1, TextVector2);
}
public string GetHighFreqWord()
{
string UniteQuesText = TextStr1 + TextStr2;
Hubble.Analyzer.PanGuAnalyzer analyzer = new Hubble.Analyzer.PanGuAnalyzer();
IEnumerable<Hubble.Core.Entity.WordInfo> words = analyzer.Tokenize(UniteQuesText);
StringBuilder result = new StringBuilder();
List<string> wordList = new List<string>();
foreach (Hubble.Core.Entity.WordInfo word in words)
{
if (word.Word.Length <= 1) continue;
if (word.Rank <= 1) continue;
if (Regex.Matches(word.Word, "[0-9]").Count > 0) continue;
wordList.Add(word.Word);
}
if (wordList.Count < 10)
{
foreach (string word in wordList)
{
if (result.ToString().IndexOf(word) < 0)
result.Append((result.Length > 0) ? " " + word : word);
}
}
else
{
var res = Convert.ToInt32(Math.Floor(Convert.ToDouble(wordList.Count / 3)));//前中后三段
for (var i = 0; i < 3; i++)//3段
{
for (var j = 0; j < 3; j++)//每一段取前3个
{
var word = wordList[i * res + j];
if (result.ToString().IndexOf(word) < 0)
result.Append((result.Length > 0) ? " " + word : word);
}
}
}
return result.ToString();
}
/// <summary>
/// 清理
/// </summary>
public void Close()
{
TextStr1 = "";
TextStr2 = "";
TextStr1 = null;
TextStr2 = null;
}
/// <summary>
/// 获得文本的空间向量
/// </summary>
/// <param name="QuesTextFull"></param>
/// <returns></returns>
private string GetTextVector(string QuesText, string UniteQuesText)
{
//PanGuTokenizer Tokenizer = new PanGuTokenizer();
//ICollection<WordInfo> words = Tokenizer.SegmentToWordInfos(UniteQuesText);//所有的单词
//Tokenizer.Close();
Hubble.Analyzer.PanGuAnalyzer analyzer = new Hubble.Analyzer.PanGuAnalyzer();
IEnumerable<Hubble.Core.Entity.WordInfo> words = analyzer.Tokenize(UniteQuesText);
List<string> wordList = new List<string>();
List<double> wordWeight = new List<double>();
StringBuilder result = new StringBuilder();
MatchCollection mc = null;
double tmpweight = 0;
double maxweight = 0;
foreach (Hubble.Core.Entity.WordInfo word in words)
{
if (word.Word.Length <= 1 || wordList.Contains(word.Word)) continue;
if (word.Word.Contains("+") || word.Word.Contains("-") || word.Word.Contains("+") || word.Word.Contains("—")) continue;//2013.2.21添加
mc = Regex.Matches(QuesText, word.Word);
tmpweight = mc.Count;
wordList.Add(word.Word);
wordWeight.Add(tmpweight);
if (tmpweight > maxweight) maxweight = tmpweight;
}
double calu = 0;
foreach (double weight in wordWeight)
{
calu = Math.Round((weight / maxweight), 3);
if (result.Length == 0)
result.Append(calu.ToString());
else
result.Append("," + calu.ToString());
}
wordList.Clear();
wordWeight.Clear();
return result.ToString();
}
//计算两空间向量的相似度(余弦定理)
private double CalculateVectorCos(string vector1, string vector2)
{
string[] vector1List = vector1.Split(',');
string[] vector2List = vector2.Split(',');
List<double> v1 = new List<double>();
List<double> v2 = new List<double>();
double tmp = 0;
foreach (string v in vector1List)
{
tmp = double.Parse(v);
if (tmp >= 0) v1.Add(tmp);
}
foreach (string v in vector2List)
{
tmp = double.Parse(v);
if (tmp >= 0) v2.Add(tmp);
}
if (v1.Count < v2.Count)
{
int s1 = v2.Count - v1.Count;
for (int i = 0; i < s1; i++) v1.Add(0);
}
else if (v1.Count > v2.Count)
{
int s2 = v1.Count - v2.Count;
for (int i = 0; i < s2; i++) v2.Add(0);
}
double a = 0, b = 0, c = 0;
for (int i = 0, j = v1.Count; i < j; i++)
{
a += v1[i] * v2[i];
b += v1[i] * v1[i];
c += v2[i] * v2[i];
}
return a / (Math.Sqrt(b) * Math.Sqrt(c));//使用余弦定理计算相似度
}
}View Code
这些代码就是一般的查重程序,都是根据上面的公式进行计算,计算出重复数据;没有什么特别注意的,只要按照公式来就行
下面主要讲一下带图片的查重程序:这是网上流行的几种图片查重算法,第一种是我自创的。


public static int BitmapCompare1(Bitmap bitmap1, Bitmap bitmap2)
{
int result = 0;
int result1 = 0;
if (bitmap1 == null || bitmap2 == null)
return -1;
for (int i = 0; i < bitmap1.Width; i++)
{
for (int j = 0; j < bitmap1.Height; j++)
{
Color color1 = bitmap1.GetPixel(i, j);
if (color1 == Color.FromArgb(0, 0, 0, 0))
{
result++;
}
}
}
for (int i1 = 0; i1 < bitmap2.Width; i1++)
{
for (int j1 = 0; j1 < bitmap2.Height; j1++)
{
Color color2 = bitmap2.GetPixel(i1, j1);
if (color2 == Color.FromArgb(0, 0, 0, 0))
{
result1++;
}
}
}
return result - result1;
}
/// <summary>
/// 比较两幅图片是否一致(使用memcmp方式)
/// </summary>
/// <param name="bitmap1">图片1</param>
/// <param name="bitmap2">图片2</param>
/// <returns>如果两幅图片相同,返回0;如果图片1小于图片2,返回小于0的值;如果图片1大于图片2,返回大于0的值。</returns>
public static int BitmapCompare2(Bitmap bitmap1, Bitmap bitmap2)
{
int result = 0; //假设两幅图片相同
if (bitmap1 == null || bitmap2 == null)
return -1;
if (bitmap1.Width == bitmap2.Width && bitmap1.Height == bitmap2.Height)
{
BitmapData bmd1 = bitmap1.LockBits(new Rectangle(0, 0, bitmap1.Width, bitmap1.Height), ImageLockMode.ReadOnly, bitmap1.PixelFormat);
BitmapData bmd2 = bitmap2.LockBits(new Rectangle(0, 0, bitmap2.Width, bitmap2.Height), ImageLockMode.ReadOnly, bitmap2.PixelFormat);
int bytes = bmd1.Stride * bitmap1.Height;
byte[] buff1 = new byte[bytes];
byte[] buff2 = new byte[bytes];
Marshal.Copy(bmd1.Scan0, buff1, 0, Marshal.SizeOf(typeof(byte)) * bytes);
Marshal.Copy(bmd2.Scan0, buff2, 0, Marshal.SizeOf(typeof(byte)) * bytes);
result = MemoryCompare(buff1, buff2);
bitmap1.UnlockBits(bmd1);
bitmap2.UnlockBits(bmd2);
}
else if (bitmap1.Width != bitmap2.Width)
{
result = bitmap1.Width - bitmap2.Width;
}
else if (bitmap1.Height != bitmap2.Height)
{
result = bitmap1.Height - bitmap2.Height;
}
return result;
}
/// <summary>
/// 比较两幅图片是否一致(使用Marshal.ReadByte方式)
/// </summary>
/// <param name="bitmap1">图片1</param>
/// <param name="bitmap2">图片2</param>
/// <returns>如果两幅图片相同,返回0;如果图片1小于图片2,返回小于0的值;如果图片1大于图片2,返回大于0的值。</returns>
public static int BitmapCompare3(Bitmap bitmap1, Bitmap bitmap2)
{
int result = 0; //假设两幅图片相同
if (bitmap1 == null || bitmap2 == null)
return -1;
if (bitmap1.Width == bitmap2.Width && bitmap1.Height == bitmap2.Height)
{
BitmapData bmd1 = bitmap1.LockBits(new Rectangle(0, 0, bitmap1.Width, bitmap1.Height), ImageLockMode.ReadOnly, bitmap1.PixelFormat);
BitmapData bmd2 = bitmap2.LockBits(new Rectangle(0, 0, bitmap2.Width, bitmap2.Height), ImageLockMode.ReadOnly, bitmap2.PixelFormat);
IntPtr start1 = bmd1.Scan0;
IntPtr start2 = bmd2.Scan0;
int sizeOfByte = Marshal.SizeOf(typeof(byte));
for (int i = 0; i < sizeOfByte * bmd1.Stride * bitmap1.Height; i++)
{
byte b1 = Marshal.ReadByte(start1, i);
byte b2 = Marshal.ReadByte(start2, i);
if (b1 != b2)
{
result = (int)(b1 - b2);
break;
}
}
bitmap1.UnlockBits(bmd1);
bitmap2.UnlockBits(bmd2);
}
else if (bitmap1.Width != bitmap2.Width)
{
result = bitmap1.Width - bitmap2.Width;
}
else if (bitmap1.Height != bitmap2.Height)
{
result = bitmap1.Height - bitmap2.Height;
}
return result;
}
/// <summary>
/// 比较两幅图片是否一致(使用自定义字节数组比较)
/// </summary>
/// <param name="bitmap1">图片1</param>
/// <param name="bitmap2">图片2</param>
/// <returns>如果两幅图片相同,返回0;如果图片1小于图片2,返回小于0的值;如果图片1大于图片2,返回大于0的值。</returns>
public static int BitmapCompare4(Bitmap bitmap1, Bitmap bitmap2)
{
int result = 0; //假设两幅图片相同
if (bitmap1 == null || bitmap2 == null)
return -1;
if (bitmap1.Width == bitmap2.Width && bitmap1.Height == bitmap2.Height)
{
BitmapData bmd1 = bitmap1.LockBits(new Rectangle(0, 0, bitmap1.Width, bitmap1.Height), ImageLockMode.ReadOnly, bitmap1.PixelFormat);
BitmapData bmd2 = bitmap2.LockBits(new Rectangle(0, 0, bitmap2.Width, bitmap2.Height), ImageLockMode.ReadOnly, bitmap2.PixelFormat);
int bytes = bmd1.Stride * bitmap1.Height;
byte[] buff1 = new byte[bytes];
byte[] buff2 = new byte[bytes];
Marshal.Copy(bmd1.Scan0, buff1, 0, Marshal.SizeOf(typeof(byte)) * bytes);
Marshal.Copy(bmd2.Scan0, buff2, 0, Marshal.SizeOf(typeof(byte)) * bytes);
result = MemoryCompare2(buff1, buff2);
bitmap1.UnlockBits(bmd1);
bitmap2.UnlockBits(bmd2);
}
else if (bitmap1.Width != bitmap2.Width)
{
result = bitmap1.Width - bitmap2.Width;
}
else if (bitmap1.Height != bitmap2.Height)
{
result = bitmap1.Height - bitmap2.Height;
}
return result;
}
/// <summary>
/// 用memcmp比较字节数组
/// </summary>
/// <param name="b1">字节数组1</param>
/// <param name="b2">字节数组2</param>
/// <returns>如果两个数组相同,返回0;如果数组1小于数组2,返回小于0的值;如果数组1大于数组2,返回大于0的值。</returns>
public static int MemoryCompare(byte[] b1, byte[] b2)
{
IntPtr retval = memcmp(b1, b2, new IntPtr(b1.Length));
return retval.ToInt32();
}
/// <summary>
/// 比较字节数组
/// </summary>
/// <param name="b1">字节数组1</param>
/// <param name="b2">字节数组2</param>
/// <returns>如果两个数组相同,返回0;如果数组1小于数组2,返回小于0的值;如果数组1大于数组2,返回大于0的值。</returns>
public static int MemoryCompare2(byte[] b1, byte[] b2)
{
int result = 0;
if (b1.Length != b2.Length)
result = b1.Length - b2.Length;
else
{
for (int i = 0; i < b1.Length; i++)
{
if (b1[i] != b2[i])
{
result = (int)(b1[i] - b2[i]);
break;
}
}
}
return result;
}
/// <summary>
/// memcmp API
/// </summary>
/// <param name="b1">字节数组1</param>
/// <param name="b2">字节数组2</param>
/// <returns>如果两个数组相同,返回0;如果数组1小于数组2,返回小于0的值;如果数组1大于数组2,返回大于0的值。</returns>
[DllImport("msvcrt.dll")]
private static extern IntPtr memcmp(byte[] b1, byte[] b2, IntPtr count);View Code
我自创的根本原因就是发现下面的一些比较方法在有些条件下根本不行,比如说两个图片,一个图片截图比较大,一个图片截图比较小,但是内容是相同的,如图,我用黑色背景比较容易明白,由于两幅图内容是一样的,但是图片大小不一样,所以用网上流行的那些方法根本不灵。所以由于此原因,我自己研究发明了第一种方法(保证原创)。这查重程序结合着图片查重程序一起使用才完美。


















